1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.runtime; 27 28 import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION; 30 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; 31 import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE; 32 import static jdk.nashorn.internal.runtime.CodeStore.newCodeStore; 33 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 34 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 35 import static jdk.nashorn.internal.runtime.Source.sourceFor; 36 37 import java.io.File; 38 import java.io.IOException; 39 import java.io.PrintWriter; 40 import java.lang.invoke.MethodHandle; 41 import java.lang.invoke.MethodHandles; 42 import java.lang.invoke.MethodType; 43 import java.lang.invoke.SwitchPoint; 44 import java.lang.ref.ReferenceQueue; 45 import java.lang.ref.SoftReference; 46 import java.lang.reflect.Field; 47 import java.lang.reflect.Modifier; 48 import java.net.MalformedURLException; 49 import java.net.URL; 50 import java.security.AccessControlContext; 51 import java.security.AccessController; 52 import java.security.CodeSigner; 53 import java.security.CodeSource; 54 import java.security.Permissions; 55 import java.security.PrivilegedAction; 56 import java.security.PrivilegedActionException; 57 import java.security.PrivilegedExceptionAction; 58 import java.security.ProtectionDomain; 59 import java.util.Collection; 60 import java.util.HashMap; 61 import java.util.LinkedHashMap; 62 import java.util.Map; 63 import java.util.concurrent.atomic.AtomicLong; 64 import java.util.concurrent.atomic.AtomicReference; 65 import java.util.function.Consumer; 66 import java.util.function.Supplier; 67 import java.util.logging.Level; 68 import javax.script.ScriptEngine; 69 import jdk.internal.org.objectweb.asm.ClassReader; 70 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter; 71 import jdk.nashorn.api.scripting.ClassFilter; 72 import jdk.nashorn.api.scripting.ScriptObjectMirror; 73 import jdk.nashorn.internal.codegen.Compiler; 74 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases; 75 import jdk.nashorn.internal.codegen.ObjectClassGenerator; 76 import jdk.nashorn.internal.ir.FunctionNode; 77 import jdk.nashorn.internal.ir.debug.ASTWriter; 78 import jdk.nashorn.internal.ir.debug.PrintVisitor; 79 import jdk.nashorn.internal.lookup.MethodHandleFactory; 80 import jdk.nashorn.internal.objects.Global; 81 import jdk.nashorn.internal.parser.Parser; 82 import jdk.nashorn.internal.runtime.events.RuntimeEvent; 83 import jdk.nashorn.internal.runtime.logging.DebugLogger; 84 import jdk.nashorn.internal.runtime.logging.Loggable; 85 import jdk.nashorn.internal.runtime.logging.Logger; 86 import jdk.nashorn.internal.runtime.options.LoggingOption.LoggerInfo; 87 import jdk.nashorn.internal.runtime.options.Options; 88 89 /** 90 * This class manages the global state of execution. Context is immutable. 91 */ 92 public final class Context { 93 // nashorn specific security runtime access permission names 94 /** 95 * Permission needed to pass arbitrary nashorn command line options when creating Context. 96 */ 97 public static final String NASHORN_SET_CONFIG = "nashorn.setConfig"; 98 99 /** 100 * Permission needed to create Nashorn Context instance. 101 */ 102 public static final String NASHORN_CREATE_CONTEXT = "nashorn.createContext"; 103 104 /** 105 * Permission needed to create Nashorn Global instance. 106 */ 107 public static final String NASHORN_CREATE_GLOBAL = "nashorn.createGlobal"; 108 109 /** 110 * Permission to get current Nashorn Context from thread local storage. 111 */ 112 public static final String NASHORN_GET_CONTEXT = "nashorn.getContext"; 113 114 /** 115 * Permission to use Java reflection/jsr292 from script code. 116 */ 117 public static final String NASHORN_JAVA_REFLECTION = "nashorn.JavaReflection"; 118 119 /** 120 * Permission to enable nashorn debug mode. 121 */ 122 public static final String NASHORN_DEBUG_MODE = "nashorn.debugMode"; 123 124 // nashorn load psuedo URL prefixes 125 private static final String LOAD_CLASSPATH = "classpath:"; 126 private static final String LOAD_FX = "fx:"; 127 private static final String LOAD_NASHORN = "nashorn:"; 128 129 private static MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 130 private static MethodType CREATE_PROGRAM_FUNCTION_TYPE = MethodType.methodType(ScriptFunction.class, ScriptObject.class); 131 132 /** 133 * Keeps track of which builtin prototypes and properties have been relinked 134 * Currently we are conservative and associate the name of a builtin class with all 135 * its properties, so it's enough to invalidate a property to break all assumptions 136 * about a prototype. This can be changed to a more fine grained approach, but no one 137 * ever needs this, given the very rare occurance of swapping out only parts of 138 * a builtin v.s. the entire builtin object 139 */ 140 private final Map<String, SwitchPoint> builtinSwitchPoints = new HashMap<>(); 141 142 /* Force DebuggerSupport to be loaded. */ 143 static { 144 DebuggerSupport.FORCELOAD = true; 145 } 146 147 /** 148 * ContextCodeInstaller that has the privilege of installing classes in the Context. 149 * Can only be instantiated from inside the context and is opaque to other classes 150 */ 151 public static class ContextCodeInstaller implements CodeInstaller<ScriptEnvironment> { 152 private final Context context; 153 private final ScriptLoader loader; 154 private final CodeSource codeSource; 155 private int usageCount = 0; 156 private int bytesDefined = 0; 157 158 // We reuse this installer for 10 compilations or 200000 defined bytes. Usually the first condition 159 // will occur much earlier, the second is a safety measure for very large scripts/functions. 160 private final static int MAX_USAGES = 10; 161 private final static int MAX_BYTES_DEFINED = 200_000; 162 163 private ContextCodeInstaller(final Context context, final ScriptLoader loader, final CodeSource codeSource) { 164 this.context = context; 165 this.loader = loader; 166 this.codeSource = codeSource; 167 } 168 169 /** 170 * Return the script environment for this installer 171 * @return ScriptEnvironment 172 */ 173 @Override 174 public ScriptEnvironment getOwner() { 175 return context.env; 176 } 177 178 @Override 179 public Class<?> install(final String className, final byte[] bytecode) { 180 usageCount++; 181 bytesDefined += bytecode.length; 182 final String binaryName = Compiler.binaryName(className); 183 return loader.installClass(binaryName, bytecode, codeSource); 184 } 185 186 @Override 187 public void initialize(final Collection<Class<?>> classes, final Source source, final Object[] constants) { 188 try { 189 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { 190 @Override 191 public Void run() throws Exception { 192 for (final Class<?> clazz : classes) { 193 //use reflection to write source and constants table to installed classes 194 final Field sourceField = clazz.getDeclaredField(SOURCE.symbolName()); 195 sourceField.setAccessible(true); 196 sourceField.set(null, source); 197 198 final Field constantsField = clazz.getDeclaredField(CONSTANTS.symbolName()); 199 constantsField.setAccessible(true); 200 constantsField.set(null, constants); 201 } 202 return null; 203 } 204 }); 205 } catch (final PrivilegedActionException e) { 206 throw new RuntimeException(e); 207 } 208 } 209 210 @Override 211 public void verify(final byte[] code) { 212 context.verify(code); 213 } 214 215 @Override 216 public long getUniqueScriptId() { 217 return context.getUniqueScriptId(); 218 } 219 220 @Override 221 public void storeScript(final String cacheKey, final Source source, final String mainClassName, 222 final Map<String,byte[]> classBytes, final Map<Integer, FunctionInitializer> initializers, 223 final Object[] constants, final int compilationId) { 224 if (context.codeStore != null) { 225 context.codeStore.store(cacheKey, source, mainClassName, classBytes, initializers, constants, compilationId); 226 } 227 } 228 229 @Override 230 public StoredScript loadScript(final Source source, final String functionKey) { 231 if (context.codeStore != null) { 232 return context.codeStore.load(source, functionKey); 233 } 234 return null; 235 } 236 237 @Override 238 public CodeInstaller<ScriptEnvironment> withNewLoader() { 239 // Reuse this installer if we're within our limits. 240 if (usageCount < MAX_USAGES && bytesDefined < MAX_BYTES_DEFINED) { 241 return this; 242 } 243 return new ContextCodeInstaller(context, context.createNewLoader(), codeSource); 244 } 245 246 @Override 247 public boolean isCompatibleWith(final CodeInstaller<ScriptEnvironment> other) { 248 if (other instanceof ContextCodeInstaller) { 249 final ContextCodeInstaller cci = (ContextCodeInstaller)other; 250 return cci.context == context && cci.codeSource == codeSource; 251 } 252 return false; 253 } 254 } 255 256 /** Is Context global debug mode enabled ? */ 257 public static final boolean DEBUG = Options.getBooleanProperty("nashorn.debug"); 258 259 private static final ThreadLocal<Global> currentGlobal = new ThreadLocal<>(); 260 261 // in-memory cache for loaded classes 262 private ClassCache classCache; 263 264 // persistent code store 265 private CodeStore codeStore; 266 267 // A factory for linking global properties as constant method handles. It is created when the first Global 268 // is created, and invalidated forever once the second global is created. 269 private final AtomicReference<GlobalConstants> globalConstantsRef = new AtomicReference<>(); 270 271 /** 272 * Get the current global scope 273 * @return the current global scope 274 */ 275 public static Global getGlobal() { 276 // This class in a package.access protected package. 277 // Trusted code only can call this method. 278 return currentGlobal.get(); 279 } 280 281 /** 282 * Set the current global scope 283 * @param global the global scope 284 */ 285 public static void setGlobal(final ScriptObject global) { 286 if (global != null && !(global instanceof Global)) { 287 throw new IllegalArgumentException("not a global!"); 288 } 289 setGlobal((Global)global); 290 } 291 292 /** 293 * Set the current global scope 294 * @param global the global scope 295 */ 296 public static void setGlobal(final Global global) { 297 // This class in a package.access protected package. 298 // Trusted code only can call this method. 299 assert getGlobal() != global; 300 //same code can be cached between globals, then we need to invalidate method handle constants 301 if (global != null) { 302 final GlobalConstants globalConstants = getContext(global).getGlobalConstants(); 303 if (globalConstants != null) { 304 globalConstants.invalidateAll(); 305 } 306 } 307 currentGlobal.set(global); 308 } 309 310 /** 311 * Get context of the current global 312 * @return current global scope's context. 313 */ 314 public static Context getContext() { 315 final SecurityManager sm = System.getSecurityManager(); 316 if (sm != null) { 317 sm.checkPermission(new RuntimePermission(NASHORN_GET_CONTEXT)); 318 } 319 return getContextTrusted(); 320 } 321 322 /** 323 * Get current context's error writer 324 * 325 * @return error writer of the current context 326 */ 327 public static PrintWriter getCurrentErr() { 328 final ScriptObject global = getGlobal(); 329 return (global != null)? global.getContext().getErr() : new PrintWriter(System.err); 330 } 331 332 /** 333 * Output text to this Context's error stream 334 * @param str text to write 335 */ 336 public static void err(final String str) { 337 err(str, true); 338 } 339 340 /** 341 * Output text to this Context's error stream, optionally with 342 * a newline afterwards 343 * 344 * @param str text to write 345 * @param crlf write a carriage return/new line after text 346 */ 347 public static void err(final String str, final boolean crlf) { 348 final PrintWriter err = Context.getCurrentErr(); 349 if (err != null) { 350 if (crlf) { 351 err.println(str); 352 } else { 353 err.print(str); 354 } 355 } 356 } 357 358 /** Current environment. */ 359 private final ScriptEnvironment env; 360 361 /** is this context in strict mode? Cached from env. as this is used heavily. */ 362 final boolean _strict; 363 364 /** class loader to resolve classes from script. */ 365 private final ClassLoader appLoader; 366 367 /** Class loader to load classes from -classpath option, if set. */ 368 private final ClassLoader classPathLoader; 369 370 /** Class loader to load classes compiled from scripts. */ 371 private final ScriptLoader scriptLoader; 372 373 /** Current error manager. */ 374 private final ErrorManager errors; 375 376 /** Unique id for script. Used only when --loader-per-compile=false */ 377 private final AtomicLong uniqueScriptId; 378 379 /** Optional class filter to use for Java classes. Can be null. */ 380 private final ClassFilter classFilter; 381 382 private static final ClassLoader myLoader = Context.class.getClassLoader(); 383 private static final StructureLoader sharedLoader; 384 385 /*package-private*/ @SuppressWarnings("static-method") 386 ClassLoader getSharedLoader() { 387 return sharedLoader; 388 } 389 390 private static AccessControlContext createNoPermAccCtxt() { 391 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, new Permissions()) }); 392 } 393 394 private static AccessControlContext createPermAccCtxt(final String permName) { 395 final Permissions perms = new Permissions(); 396 perms.add(new RuntimePermission(permName)); 397 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) }); 398 } 399 400 private static final AccessControlContext NO_PERMISSIONS_ACC_CTXT = createNoPermAccCtxt(); 401 private static final AccessControlContext CREATE_LOADER_ACC_CTXT = createPermAccCtxt("createClassLoader"); 402 private static final AccessControlContext CREATE_GLOBAL_ACC_CTXT = createPermAccCtxt(NASHORN_CREATE_GLOBAL); 403 404 static { 405 sharedLoader = AccessController.doPrivileged(new PrivilegedAction<StructureLoader>() { 406 @Override 407 public StructureLoader run() { 408 return new StructureLoader(myLoader); 409 } 410 }, CREATE_LOADER_ACC_CTXT); 411 } 412 413 /** 414 * ThrowErrorManager that throws ParserException upon error conditions. 415 */ 416 public static class ThrowErrorManager extends ErrorManager { 417 @Override 418 public void error(final String message) { 419 throw new ParserException(message); 420 } 421 422 @Override 423 public void error(final ParserException e) { 424 throw e; 425 } 426 } 427 428 /** 429 * Constructor 430 * 431 * @param options options from command line or Context creator 432 * @param errors error manger 433 * @param appLoader application class loader 434 */ 435 public Context(final Options options, final ErrorManager errors, final ClassLoader appLoader) { 436 this(options, errors, appLoader, (ClassFilter)null); 437 } 438 439 /** 440 * Constructor 441 * 442 * @param options options from command line or Context creator 443 * @param errors error manger 444 * @param appLoader application class loader 445 * @param classFilter class filter to use 446 */ 447 public Context(final Options options, final ErrorManager errors, final ClassLoader appLoader, final ClassFilter classFilter) { 448 this(options, errors, new PrintWriter(System.out, true), new PrintWriter(System.err, true), appLoader, classFilter); 449 } 450 451 /** 452 * Constructor 453 * 454 * @param options options from command line or Context creator 455 * @param errors error manger 456 * @param out output writer for this Context 457 * @param err error writer for this Context 458 * @param appLoader application class loader 459 */ 460 public Context(final Options options, final ErrorManager errors, final PrintWriter out, final PrintWriter err, final ClassLoader appLoader) { 461 this(options, errors, out, err, appLoader, (ClassFilter)null); 462 } 463 464 /** 465 * Constructor 466 * 467 * @param options options from command line or Context creator 468 * @param errors error manger 469 * @param out output writer for this Context 470 * @param err error writer for this Context 471 * @param appLoader application class loader 472 * @param classFilter class filter to use 473 */ 474 public Context(final Options options, final ErrorManager errors, final PrintWriter out, final PrintWriter err, final ClassLoader appLoader, final ClassFilter classFilter) { 475 final SecurityManager sm = System.getSecurityManager(); 476 if (sm != null) { 477 sm.checkPermission(new RuntimePermission(NASHORN_CREATE_CONTEXT)); 478 } 479 480 this.classFilter = classFilter; 481 this.env = new ScriptEnvironment(options, out, err); 482 this._strict = env._strict; 483 this.appLoader = appLoader; 484 if (env._loader_per_compile) { 485 this.scriptLoader = null; 486 this.uniqueScriptId = null; 487 } else { 488 this.scriptLoader = createNewLoader(); 489 this.uniqueScriptId = new AtomicLong(); 490 } 491 this.errors = errors; 492 493 // if user passed -classpath option, make a class loader with that and set it as 494 // thread context class loader so that script can access classes from that path. 495 final String classPath = options.getString("classpath"); 496 if (!env._compile_only && classPath != null && !classPath.isEmpty()) { 497 // make sure that caller can create a class loader. 498 if (sm != null) { 499 sm.checkPermission(new RuntimePermission("createClassLoader")); 500 } 501 this.classPathLoader = NashornLoader.createClassLoader(classPath); 502 } else { 503 this.classPathLoader = null; 504 } 505 506 final int cacheSize = env._class_cache_size; 507 if (cacheSize > 0) { 508 classCache = new ClassCache(this, cacheSize); 509 } 510 511 if (env._persistent_cache) { 512 codeStore = newCodeStore(this); 513 } 514 515 // print version info if asked. 516 if (env._version) { 517 getErr().println("nashorn " + Version.version()); 518 } 519 520 if (env._fullversion) { 521 getErr().println("nashorn full version " + Version.fullVersion()); 522 } 523 524 initLoggers(); 525 } 526 527 528 /** 529 * Get the class filter for this context 530 * @return class filter 531 */ 532 public ClassFilter getClassFilter() { 533 return classFilter; 534 } 535 536 /** 537 * Returns the factory for constant method handles for global properties. The returned factory can be 538 * invalidated if this Context has more than one Global. 539 * @return the factory for constant method handles for global properties. 540 */ 541 GlobalConstants getGlobalConstants() { 542 return globalConstantsRef.get(); 543 } 544 545 /** 546 * Get the error manager for this context 547 * @return error manger 548 */ 549 public ErrorManager getErrorManager() { 550 return errors; 551 } 552 553 /** 554 * Get the script environment for this context 555 * @return script environment 556 */ 557 public ScriptEnvironment getEnv() { 558 return env; 559 } 560 561 /** 562 * Get the output stream for this context 563 * @return output print writer 564 */ 565 public PrintWriter getOut() { 566 return env.getOut(); 567 } 568 569 /** 570 * Get the error stream for this context 571 * @return error print writer 572 */ 573 public PrintWriter getErr() { 574 return env.getErr(); 575 } 576 577 /** 578 * Get the PropertyMap of the current global scope 579 * @return the property map of the current global scope 580 */ 581 public static PropertyMap getGlobalMap() { 582 return Context.getGlobal().getMap(); 583 } 584 585 /** 586 * Compile a top level script. 587 * 588 * @param source the source 589 * @param scope the scope 590 * 591 * @return top level function for script 592 */ 593 public ScriptFunction compileScript(final Source source, final ScriptObject scope) { 594 return compileScript(source, scope, this.errors); 595 } 596 597 /** 598 * Interface to represent compiled code that can be re-used across many 599 * global scope instances 600 */ 601 public static interface MultiGlobalCompiledScript { 602 /** 603 * Obtain script function object for a specific global scope object. 604 * 605 * @param newGlobal global scope for which function object is obtained 606 * @return script function for script level expressions 607 */ 608 public ScriptFunction getFunction(final Global newGlobal); 609 } 610 611 /** 612 * Compile a top level script. 613 * 614 * @param source the script source 615 * @return reusable compiled script across many global scopes. 616 */ 617 public MultiGlobalCompiledScript compileScript(final Source source) { 618 final Class<?> clazz = compile(source, this.errors, this._strict); 619 final MethodHandle createProgramFunctionHandle = getCreateProgramFunctionHandle(clazz); 620 621 return new MultiGlobalCompiledScript() { 622 @Override 623 public ScriptFunction getFunction(final Global newGlobal) { 624 return invokeCreateProgramFunctionHandle(createProgramFunctionHandle, newGlobal); 625 } 626 }; 627 } 628 629 /** 630 * Entry point for {@code eval} 631 * 632 * @param initialScope The scope of this eval call 633 * @param string Evaluated code as a String 634 * @param callThis "this" to be passed to the evaluated code 635 * @param location location of the eval call 636 * @param strict is this {@code eval} call from a strict mode code? 637 * @return the return value of the {@code eval} 638 */ 639 public Object eval(final ScriptObject initialScope, final String string, 640 final Object callThis, final Object location, final boolean strict) { 641 return eval(initialScope, string, callThis, location, strict, false); 642 } 643 644 /** 645 * Entry point for {@code eval} 646 * 647 * @param initialScope The scope of this eval call 648 * @param string Evaluated code as a String 649 * @param callThis "this" to be passed to the evaluated code 650 * @param location location of the eval call 651 * @param strict is this {@code eval} call from a strict mode code? 652 * @param evalCall is this called from "eval" builtin? 653 * 654 * @return the return value of the {@code eval} 655 */ 656 public Object eval(final ScriptObject initialScope, final String string, 657 final Object callThis, final Object location, final boolean strict, final boolean evalCall) { 658 final String file = location == UNDEFINED || location == null ? "<eval>" : location.toString(); 659 final Source source = sourceFor(file, string, evalCall); 660 final boolean directEval = location != UNDEFINED; // is this direct 'eval' call or indirectly invoked eval? 661 final Global global = Context.getGlobal(); 662 ScriptObject scope = initialScope; 663 664 // ECMA section 10.1.1 point 2 says eval code is strict if it begins 665 // with "use strict" directive or eval direct call itself is made 666 // from from strict mode code. We are passed with caller's strict mode. 667 boolean strictFlag = directEval && strict; 668 669 Class<?> clazz = null; 670 try { 671 clazz = compile(source, new ThrowErrorManager(), strictFlag); 672 } catch (final ParserException e) { 673 e.throwAsEcmaException(global); 674 return null; 675 } 676 677 if (!strictFlag) { 678 // We need to get strict mode flag from compiled class. This is 679 // because eval code may start with "use strict" directive. 680 try { 681 strictFlag = clazz.getField(STRICT_MODE.symbolName()).getBoolean(null); 682 } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { 683 //ignored 684 strictFlag = false; 685 } 686 } 687 688 // In strict mode, eval does not instantiate variables and functions 689 // in the caller's environment. A new environment is created! 690 if (strictFlag) { 691 // Create a new scope object 692 final ScriptObject strictEvalScope = global.newObject(); 693 694 // bless it as a "scope" 695 strictEvalScope.setIsScope(); 696 697 // set given scope to be it's proto so that eval can still 698 // access caller environment vars in the new environment. 699 strictEvalScope.setProto(scope); 700 scope = strictEvalScope; 701 } 702 703 final ScriptFunction func = getProgramFunction(clazz, scope); 704 Object evalThis; 705 if (directEval) { 706 evalThis = (callThis != UNDEFINED && callThis != null) || strictFlag ? callThis : global; 707 } else { 708 evalThis = global; 709 } 710 711 return ScriptRuntime.apply(func, evalThis); 712 } 713 714 private static Source loadInternal(final String srcStr, final String prefix, final String resourcePath) { 715 if (srcStr.startsWith(prefix)) { 716 final String resource = resourcePath + srcStr.substring(prefix.length()); 717 // NOTE: even sandbox scripts should be able to load scripts in nashorn: scheme 718 // These scripts are always available and are loaded from nashorn.jar's resources. 719 return AccessController.doPrivileged( 720 new PrivilegedAction<Source>() { 721 @Override 722 public Source run() { 723 try { 724 final URL resURL = Context.class.getResource(resource); 725 return resURL != null ? sourceFor(srcStr, resURL) : null; 726 } catch (final IOException exp) { 727 return null; 728 } 729 } 730 }); 731 } 732 733 return null; 734 } 735 736 /** 737 * Implementation of {@code load} Nashorn extension. Load a script file from a source 738 * expression 739 * 740 * @param scope the scope 741 * @param from source expression for script 742 * 743 * @return return value for load call (undefined) 744 * 745 * @throws IOException if source cannot be found or loaded 746 */ 747 public Object load(final ScriptObject scope, final Object from) throws IOException { 748 final Object src = from instanceof ConsString ? from.toString() : from; 749 Source source = null; 750 751 // load accepts a String (which could be a URL or a file name), a File, a URL 752 // or a ScriptObject that has "name" and "source" (string valued) properties. 753 if (src instanceof String) { 754 final String srcStr = (String)src; 755 if (srcStr.startsWith(LOAD_CLASSPATH)) { 756 final URL url = getResourceURL(srcStr.substring(LOAD_CLASSPATH.length())); 757 source = url != null ? sourceFor(url.toString(), url) : null; 758 } else { 759 final File file = new File(srcStr); 760 if (srcStr.indexOf(':') != -1) { 761 if ((source = loadInternal(srcStr, LOAD_NASHORN, "resources/")) == null && 762 (source = loadInternal(srcStr, LOAD_FX, "resources/fx/")) == null) { 763 URL url; 764 try { 765 //check for malformed url. if malformed, it may still be a valid file 766 url = new URL(srcStr); 767 } catch (final MalformedURLException e) { 768 url = file.toURI().toURL(); 769 } 770 source = sourceFor(url.toString(), url); 771 } 772 } else if (file.isFile()) { 773 source = sourceFor(srcStr, file); 774 } 775 } 776 } else if (src instanceof File && ((File)src).isFile()) { 777 final File file = (File)src; 778 source = sourceFor(file.getName(), file); 779 } else if (src instanceof URL) { 780 final URL url = (URL)src; 781 source = sourceFor(url.toString(), url); 782 } else if (src instanceof ScriptObject) { 783 final ScriptObject sobj = (ScriptObject)src; 784 if (sobj.has("script") && sobj.has("name")) { 785 final String script = JSType.toString(sobj.get("script")); 786 final String name = JSType.toString(sobj.get("name")); 787 source = sourceFor(name, script); 788 } 789 } else if (src instanceof Map) { 790 final Map<?,?> map = (Map<?,?>)src; 791 if (map.containsKey("script") && map.containsKey("name")) { 792 final String script = JSType.toString(map.get("script")); 793 final String name = JSType.toString(map.get("name")); 794 source = sourceFor(name, script); 795 } 796 } 797 798 if (source != null) { 799 return evaluateSource(source, scope, scope); 800 } 801 802 throw typeError("cant.load.script", ScriptRuntime.safeToString(from)); 803 } 804 805 /** 806 * Implementation of {@code loadWithNewGlobal} Nashorn extension. Load a script file from a source 807 * expression, after creating a new global scope. 808 * 809 * @param from source expression for script 810 * @param args (optional) arguments to be passed to the loaded script 811 * 812 * @return return value for load call (undefined) 813 * 814 * @throws IOException if source cannot be found or loaded 815 */ 816 public Object loadWithNewGlobal(final Object from, final Object...args) throws IOException { 817 final Global oldGlobal = getGlobal(); 818 final Global newGlobal = AccessController.doPrivileged(new PrivilegedAction<Global>() { 819 @Override 820 public Global run() { 821 try { 822 return newGlobal(); 823 } catch (final RuntimeException e) { 824 if (Context.DEBUG) { 825 e.printStackTrace(); 826 } 827 throw e; 828 } 829 } 830 }, CREATE_GLOBAL_ACC_CTXT); 831 // initialize newly created Global instance 832 initGlobal(newGlobal); 833 setGlobal(newGlobal); 834 835 final Object[] wrapped = args == null? ScriptRuntime.EMPTY_ARRAY : ScriptObjectMirror.wrapArray(args, oldGlobal); 836 newGlobal.put("arguments", newGlobal.wrapAsObject(wrapped), env._strict); 837 838 try { 839 // wrap objects from newGlobal's world as mirrors - but if result 840 // is from oldGlobal's world, unwrap it! 841 return ScriptObjectMirror.unwrap(ScriptObjectMirror.wrap(load(newGlobal, from), newGlobal), oldGlobal); 842 } finally { 843 setGlobal(oldGlobal); 844 } 845 } 846 847 /** 848 * Load or get a structure class. Structure class names are based on the number of parameter fields 849 * and {@link AccessorProperty} fields in them. Structure classes are used to represent ScriptObjects 850 * 851 * @see ObjectClassGenerator 852 * @see AccessorProperty 853 * @see ScriptObject 854 * 855 * @param fullName full name of class, e.g. jdk.nashorn.internal.objects.JO2P1 contains 2 fields and 1 parameter. 856 * 857 * @return the {@code Class<?>} for this structure 858 * 859 * @throws ClassNotFoundException if structure class cannot be resolved 860 */ 861 @SuppressWarnings("unchecked") 862 public static Class<? extends ScriptObject> forStructureClass(final String fullName) throws ClassNotFoundException { 863 if (System.getSecurityManager() != null && !StructureLoader.isStructureClass(fullName)) { 864 throw new ClassNotFoundException(fullName); 865 } 866 return (Class<? extends ScriptObject>)Class.forName(fullName, true, sharedLoader); 867 } 868 869 /** 870 * Checks that the given Class can be accessed from no permissions context. 871 * 872 * @param clazz Class object 873 * @throws SecurityException if not accessible 874 */ 875 public static void checkPackageAccess(final Class<?> clazz) { 876 final SecurityManager sm = System.getSecurityManager(); 877 if (sm != null) { 878 Class<?> bottomClazz = clazz; 879 while (bottomClazz.isArray()) { 880 bottomClazz = bottomClazz.getComponentType(); 881 } 882 checkPackageAccess(sm, bottomClazz.getName()); 883 } 884 } 885 886 /** 887 * Checks that the given package name can be accessed from no permissions context. 888 * 889 * @param pkgName package name 890 * @throws SecurityException if not accessible 891 */ 892 public static void checkPackageAccess(final String pkgName) { 893 final SecurityManager sm = System.getSecurityManager(); 894 if (sm != null) { 895 checkPackageAccess(sm, pkgName.endsWith(".") ? pkgName : pkgName + "."); 896 } 897 } 898 899 /** 900 * Checks that the given package can be accessed from no permissions context. 901 * 902 * @param sm current security manager instance 903 * @param fullName fully qualified package name 904 * @throw SecurityException if not accessible 905 */ 906 private static void checkPackageAccess(final SecurityManager sm, final String fullName) { 907 sm.getClass(); // null check 908 final int index = fullName.lastIndexOf('.'); 909 if (index != -1) { 910 final String pkgName = fullName.substring(0, index); 911 AccessController.doPrivileged(new PrivilegedAction<Void>() { 912 @Override 913 public Void run() { 914 sm.checkPackageAccess(pkgName); 915 return null; 916 } 917 }, NO_PERMISSIONS_ACC_CTXT); 918 } 919 } 920 921 /** 922 * Checks that the given Class can be accessed from no permissions context. 923 * 924 * @param clazz Class object 925 * @return true if package is accessible, false otherwise 926 */ 927 private static boolean isAccessiblePackage(final Class<?> clazz) { 928 try { 929 checkPackageAccess(clazz); 930 return true; 931 } catch (final SecurityException se) { 932 return false; 933 } 934 } 935 936 /** 937 * Checks that the given Class is public and it can be accessed from no permissions context. 938 * 939 * @param clazz Class object to check 940 * @return true if Class is accessible, false otherwise 941 */ 942 public static boolean isAccessibleClass(final Class<?> clazz) { 943 return Modifier.isPublic(clazz.getModifiers()) && Context.isAccessiblePackage(clazz); 944 } 945 946 /** 947 * Lookup a Java class. This is used for JSR-223 stuff linking in from 948 * {@code jdk.nashorn.internal.objects.NativeJava} and {@code jdk.nashorn.internal.runtime.NativeJavaPackage} 949 * 950 * @param fullName full name of class to load 951 * 952 * @return the {@code Class<?>} for the name 953 * 954 * @throws ClassNotFoundException if class cannot be resolved 955 */ 956 public Class<?> findClass(final String fullName) throws ClassNotFoundException { 957 if (fullName.indexOf('[') != -1 || fullName.indexOf('/') != -1) { 958 // don't allow array class names or internal names. 959 throw new ClassNotFoundException(fullName); 960 } 961 962 // give chance to ClassFilter to filter out, if present 963 if (classFilter != null && !classFilter.exposeToScripts(fullName)) { 964 throw new ClassNotFoundException(fullName); 965 } 966 967 // check package access as soon as possible! 968 final SecurityManager sm = System.getSecurityManager(); 969 if (sm != null) { 970 checkPackageAccess(sm, fullName); 971 } 972 973 // try the script -classpath loader, if that is set 974 if (classPathLoader != null) { 975 try { 976 return Class.forName(fullName, true, classPathLoader); 977 } catch (final ClassNotFoundException ignored) { 978 // ignore, continue search 979 } 980 } 981 982 // Try finding using the "app" loader. 983 return Class.forName(fullName, true, appLoader); 984 } 985 986 /** 987 * Hook to print stack trace for a {@link Throwable} that occurred during 988 * execution 989 * 990 * @param t throwable for which to dump stack 991 */ 992 public static void printStackTrace(final Throwable t) { 993 if (Context.DEBUG) { 994 t.printStackTrace(Context.getCurrentErr()); 995 } 996 } 997 998 /** 999 * Verify generated bytecode before emission. This is called back from the 1000 * {@link ObjectClassGenerator} or the {@link Compiler}. If the "--verify-code" parameter 1001 * hasn't been given, this is a nop 1002 * 1003 * Note that verification may load classes -- we don't want to do that unless 1004 * user specified verify option. We check it here even though caller 1005 * may have already checked that flag 1006 * 1007 * @param bytecode bytecode to verify 1008 */ 1009 public void verify(final byte[] bytecode) { 1010 if (env._verify_code) { 1011 // No verification when security manager is around as verifier 1012 // may load further classes - which should be avoided. 1013 if (System.getSecurityManager() == null) { 1014 CheckClassAdapter.verify(new ClassReader(bytecode), sharedLoader, false, new PrintWriter(System.err, true)); 1015 } 1016 } 1017 } 1018 1019 /** 1020 * Create and initialize a new global scope object. 1021 * 1022 * @return the initialized global scope object. 1023 */ 1024 public Global createGlobal() { 1025 return initGlobal(newGlobal()); 1026 } 1027 1028 /** 1029 * Create a new uninitialized global scope object 1030 * @return the global script object 1031 */ 1032 public Global newGlobal() { 1033 createOrInvalidateGlobalConstants(); 1034 return new Global(this); 1035 } 1036 1037 private void createOrInvalidateGlobalConstants() { 1038 for (;;) { 1039 final GlobalConstants currentGlobalConstants = getGlobalConstants(); 1040 if (currentGlobalConstants != null) { 1041 // Subsequent invocation; we're creating our second or later Global. GlobalConstants is not safe to use 1042 // with more than one Global, as the constant method handle linkages it creates create a coupling 1043 // between the Global and the call sites in the compiled code. 1044 currentGlobalConstants.invalidateForever(); 1045 return; 1046 } 1047 final GlobalConstants newGlobalConstants = new GlobalConstants(getLogger(GlobalConstants.class)); 1048 if (globalConstantsRef.compareAndSet(null, newGlobalConstants)) { 1049 // First invocation; we're creating the first Global in this Context. Create the GlobalConstants object 1050 // for this Context. 1051 return; 1052 } 1053 1054 // If we reach here, then we started out as the first invocation, but another concurrent invocation won the 1055 // CAS race. We'll just let the loop repeat and invalidate the CAS race winner. 1056 } 1057 } 1058 1059 /** 1060 * Initialize given global scope object. 1061 * 1062 * @param global the global 1063 * @param engine the associated ScriptEngine instance, can be null 1064 * @return the initialized global scope object. 1065 */ 1066 public Global initGlobal(final Global global, final ScriptEngine engine) { 1067 // Need only minimal global object, if we are just compiling. 1068 if (!env._compile_only) { 1069 final Global oldGlobal = Context.getGlobal(); 1070 try { 1071 Context.setGlobal(global); 1072 // initialize global scope with builtin global objects 1073 global.initBuiltinObjects(engine); 1074 } finally { 1075 Context.setGlobal(oldGlobal); 1076 } 1077 } 1078 1079 return global; 1080 } 1081 1082 /** 1083 * Initialize given global scope object. 1084 * 1085 * @param global the global 1086 * @return the initialized global scope object. 1087 */ 1088 public Global initGlobal(final Global global) { 1089 return initGlobal(global, null); 1090 } 1091 1092 /** 1093 * Return the current global's context 1094 * @return current global's context 1095 */ 1096 static Context getContextTrusted() { 1097 return getContext(getGlobal()); 1098 } 1099 1100 static Context getContextTrustedOrNull() { 1101 final Global global = Context.getGlobal(); 1102 return global == null ? null : getContext(global); 1103 } 1104 1105 private static Context getContext(final Global global) { 1106 // We can't invoke Global.getContext() directly, as it's a protected override, and Global isn't in our package. 1107 // In order to access the method, we must cast it to ScriptObject first (which is in our package) and then let 1108 // virtual invocation do its thing. 1109 return ((ScriptObject)global).getContext(); 1110 } 1111 1112 /** 1113 * Try to infer Context instance from the Class. If we cannot, 1114 * then get it from the thread local variable. 1115 * 1116 * @param clazz the class 1117 * @return context 1118 */ 1119 static Context fromClass(final Class<?> clazz) { 1120 final ClassLoader loader = clazz.getClassLoader(); 1121 1122 if (loader instanceof ScriptLoader) { 1123 return ((ScriptLoader)loader).getContext(); 1124 } 1125 1126 return Context.getContextTrusted(); 1127 } 1128 1129 private URL getResourceURL(final String resName) { 1130 // try the classPathLoader if we have and then 1131 // try the appLoader if non-null. 1132 if (classPathLoader != null) { 1133 return classPathLoader.getResource(resName); 1134 } else if (appLoader != null) { 1135 return appLoader.getResource(resName); 1136 } 1137 1138 return null; 1139 } 1140 1141 private Object evaluateSource(final Source source, final ScriptObject scope, final ScriptObject thiz) { 1142 ScriptFunction script = null; 1143 1144 try { 1145 script = compileScript(source, scope, new Context.ThrowErrorManager()); 1146 } catch (final ParserException e) { 1147 e.throwAsEcmaException(); 1148 } 1149 1150 return ScriptRuntime.apply(script, thiz); 1151 } 1152 1153 private static ScriptFunction getProgramFunction(final Class<?> script, final ScriptObject scope) { 1154 if (script == null) { 1155 return null; 1156 } 1157 return invokeCreateProgramFunctionHandle(getCreateProgramFunctionHandle(script), scope); 1158 } 1159 1160 private static MethodHandle getCreateProgramFunctionHandle(final Class<?> script) { 1161 try { 1162 return LOOKUP.findStatic(script, CREATE_PROGRAM_FUNCTION.symbolName(), CREATE_PROGRAM_FUNCTION_TYPE); 1163 } catch (NoSuchMethodException | IllegalAccessException e) { 1164 throw new AssertionError("Failed to retrieve a handle for the program function for " + script.getName(), e); 1165 } 1166 } 1167 1168 private static ScriptFunction invokeCreateProgramFunctionHandle(final MethodHandle createProgramFunctionHandle, final ScriptObject scope) { 1169 try { 1170 return (ScriptFunction)createProgramFunctionHandle.invokeExact(scope); 1171 } catch (final RuntimeException|Error e) { 1172 throw e; 1173 } catch (final Throwable t) { 1174 throw new AssertionError("Failed to create a program function", t); 1175 } 1176 } 1177 1178 private ScriptFunction compileScript(final Source source, final ScriptObject scope, final ErrorManager errMan) { 1179 return getProgramFunction(compile(source, errMan, this._strict), scope); 1180 } 1181 1182 private synchronized Class<?> compile(final Source source, final ErrorManager errMan, final boolean strict) { 1183 // start with no errors, no warnings. 1184 errMan.reset(); 1185 1186 Class<?> script = findCachedClass(source); 1187 if (script != null) { 1188 final DebugLogger log = getLogger(Compiler.class); 1189 if (log.isEnabled()) { 1190 log.fine(new RuntimeEvent<>(Level.INFO, source), "Code cache hit for ", source, " avoiding recompile."); 1191 } 1192 return script; 1193 } 1194 1195 StoredScript storedScript = null; 1196 FunctionNode functionNode = null; 1197 // We only use the code store here if optimistic types are disabled. With optimistic types, initial compilation 1198 // just creates a thin wrapper, and actual code is stored per function in RecompilableScriptFunctionData. 1199 final boolean useCodeStore = codeStore != null && !env._parse_only && !env._optimistic_types; 1200 final String cacheKey = useCodeStore ? CodeStore.getCacheKey(0, null) : null; 1201 1202 if (useCodeStore) { 1203 storedScript = codeStore.load(source, cacheKey); 1204 } 1205 1206 if (storedScript == null) { 1207 functionNode = new Parser(env, source, errMan, strict, getLogger(Parser.class)).parse(); 1208 1209 if (errMan.hasErrors()) { 1210 return null; 1211 } 1212 1213 if (env._print_ast || functionNode.getFlag(FunctionNode.IS_PRINT_AST)) { 1214 getErr().println(new ASTWriter(functionNode)); 1215 } 1216 1217 if (env._print_parse || functionNode.getFlag(FunctionNode.IS_PRINT_PARSE)) { 1218 getErr().println(new PrintVisitor(functionNode, true, false)); 1219 } 1220 } 1221 1222 if (env._parse_only) { 1223 return null; 1224 } 1225 1226 final URL url = source.getURL(); 1227 final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader; 1228 final CodeSource cs = new CodeSource(url, (CodeSigner[])null); 1229 final CodeInstaller<ScriptEnvironment> installer = new ContextCodeInstaller(this, loader, cs); 1230 1231 if (storedScript == null) { 1232 final CompilationPhases phases = Compiler.CompilationPhases.COMPILE_ALL; 1233 1234 final Compiler compiler = new Compiler( 1235 this, 1236 env, 1237 installer, 1238 source, 1239 errMan, 1240 strict | functionNode.isStrict()); 1241 1242 final FunctionNode compiledFunction = compiler.compile(functionNode, phases); 1243 if (errMan.hasErrors()) { 1244 return null; 1245 } 1246 script = compiledFunction.getRootClass(); 1247 compiler.persistClassInfo(cacheKey, compiledFunction); 1248 } else { 1249 Compiler.updateCompilationId(storedScript.getCompilationId()); 1250 script = install(storedScript, source, installer); 1251 } 1252 1253 cacheClass(source, script); 1254 return script; 1255 } 1256 1257 private ScriptLoader createNewLoader() { 1258 return AccessController.doPrivileged( 1259 new PrivilegedAction<ScriptLoader>() { 1260 @Override 1261 public ScriptLoader run() { 1262 return new ScriptLoader(appLoader, Context.this); 1263 } 1264 }, CREATE_LOADER_ACC_CTXT); 1265 } 1266 1267 private long getUniqueScriptId() { 1268 return uniqueScriptId.getAndIncrement(); 1269 } 1270 1271 /** 1272 * Install a previously compiled class from the code cache. 1273 * 1274 * @param storedScript cached script containing class bytes and constants 1275 * @return main script class 1276 */ 1277 private static Class<?> install(final StoredScript storedScript, final Source source, final CodeInstaller<ScriptEnvironment> installer) { 1278 1279 final Map<String, Class<?>> installedClasses = new HashMap<>(); 1280 final Map<String, byte[]> classBytes = storedScript.getClassBytes(); 1281 final Object[] constants = storedScript.getConstants(); 1282 final String mainClassName = storedScript.getMainClassName(); 1283 final byte[] mainClassBytes = classBytes.get(mainClassName); 1284 final Class<?> mainClass = installer.install(mainClassName, mainClassBytes); 1285 final Map<Integer, FunctionInitializer> initializers = storedScript.getInitializers(); 1286 1287 installedClasses.put(mainClassName, mainClass); 1288 1289 for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) { 1290 final String className = entry.getKey(); 1291 if (className.equals(mainClassName)) { 1292 continue; 1293 } 1294 final byte[] code = entry.getValue(); 1295 1296 installedClasses.put(className, installer.install(className, code)); 1297 } 1298 1299 installer.initialize(installedClasses.values(), source, constants); 1300 1301 for (final Object constant : constants) { 1302 if (constant instanceof RecompilableScriptFunctionData) { 1303 final RecompilableScriptFunctionData data = (RecompilableScriptFunctionData) constant; 1304 data.initTransients(source, installer); 1305 final FunctionInitializer initializer = initializers.get(data.getFunctionNodeId()); 1306 if (initializer != null) { 1307 initializer.setCode(installedClasses.get(initializer.getClassName())); 1308 data.initializeCode(initializer); 1309 } 1310 } 1311 } 1312 1313 return mainClass; 1314 } 1315 1316 /** 1317 * Cache for compiled script classes. 1318 */ 1319 @SuppressWarnings("serial") 1320 @Logger(name="classcache") 1321 private static class ClassCache extends LinkedHashMap<Source, ClassReference> implements Loggable { 1322 private final int size; 1323 private final ReferenceQueue<Class<?>> queue; 1324 private final DebugLogger log; 1325 1326 ClassCache(final Context context, final int size) { 1327 super(size, 0.75f, true); 1328 this.size = size; 1329 this.queue = new ReferenceQueue<>(); 1330 this.log = initLogger(context); 1331 } 1332 1333 void cache(final Source source, final Class<?> clazz) { 1334 if (log.isEnabled()) { 1335 log.info("Caching ", source, " in class cache"); 1336 } 1337 put(source, new ClassReference(clazz, queue, source)); 1338 } 1339 1340 @Override 1341 protected boolean removeEldestEntry(final Map.Entry<Source, ClassReference> eldest) { 1342 return size() > size; 1343 } 1344 1345 @Override 1346 public ClassReference get(final Object key) { 1347 for (ClassReference ref; (ref = (ClassReference)queue.poll()) != null; ) { 1348 final Source source = ref.source; 1349 if (log.isEnabled()) { 1350 log.info("Evicting ", source, " from class cache."); 1351 } 1352 remove(source); 1353 } 1354 1355 final ClassReference ref = super.get(key); 1356 if (ref != null && log.isEnabled()) { 1357 log.info("Retrieved class reference for ", ref.source, " from class cache"); 1358 } 1359 return ref; 1360 } 1361 1362 @Override 1363 public DebugLogger initLogger(final Context context) { 1364 return context.getLogger(getClass()); 1365 } 1366 1367 @Override 1368 public DebugLogger getLogger() { 1369 return log; 1370 } 1371 1372 } 1373 1374 private static class ClassReference extends SoftReference<Class<?>> { 1375 private final Source source; 1376 1377 ClassReference(final Class<?> clazz, final ReferenceQueue<Class<?>> queue, final Source source) { 1378 super(clazz, queue); 1379 this.source = source; 1380 } 1381 } 1382 1383 // Class cache management 1384 private Class<?> findCachedClass(final Source source) { 1385 final ClassReference ref = classCache == null ? null : classCache.get(source); 1386 return ref != null ? ref.get() : null; 1387 } 1388 1389 private void cacheClass(final Source source, final Class<?> clazz) { 1390 if (classCache != null) { 1391 classCache.cache(source, clazz); 1392 } 1393 } 1394 1395 // logging 1396 private final Map<String, DebugLogger> loggers = new HashMap<>(); 1397 1398 private void initLoggers() { 1399 ((Loggable)MethodHandleFactory.getFunctionality()).initLogger(this); 1400 } 1401 1402 /** 1403 * Get a logger, given a loggable class 1404 * @param clazz a Loggable class 1405 * @return debuglogger associated with that class 1406 */ 1407 public DebugLogger getLogger(final Class<? extends Loggable> clazz) { 1408 return getLogger(clazz, null); 1409 } 1410 1411 /** 1412 * Get a logger, given a loggable class 1413 * @param clazz a Loggable class 1414 * @param initHook an init hook - if this is the first time the logger is created in the context, run the init hook 1415 * @return debuglogger associated with that class 1416 */ 1417 public DebugLogger getLogger(final Class<? extends Loggable> clazz, final Consumer<DebugLogger> initHook) { 1418 final String name = getLoggerName(clazz); 1419 DebugLogger logger = loggers.get(name); 1420 if (logger == null) { 1421 if (!env.hasLogger(name)) { 1422 return DebugLogger.DISABLED_LOGGER; 1423 } 1424 final LoggerInfo info = env._loggers.get(name); 1425 logger = new DebugLogger(name, info.getLevel(), info.isQuiet()); 1426 if (initHook != null) { 1427 initHook.accept(logger); 1428 } 1429 loggers.put(name, logger); 1430 } 1431 return logger; 1432 } 1433 1434 /** 1435 * Given a Loggable class, weave debug info info a method handle for that logger. 1436 * Level.INFO is used 1437 * 1438 * @param clazz loggable 1439 * @param mh method handle 1440 * @param text debug printout to add 1441 * 1442 * @return instrumented method handle, or null if logger not enabled 1443 */ 1444 public MethodHandle addLoggingToHandle(final Class<? extends Loggable> clazz, final MethodHandle mh, final Supplier<String> text) { 1445 return addLoggingToHandle(clazz, Level.INFO, mh, Integer.MAX_VALUE, false, text); 1446 } 1447 1448 /** 1449 * Given a Loggable class, weave debug info info a method handle for that logger. 1450 * 1451 * @param clazz loggable 1452 * @param level log level 1453 * @param mh method handle 1454 * @param paramStart first parameter to print 1455 * @param printReturnValue should we print the return vaulue? 1456 * @param text debug printout to add 1457 * 1458 * @return instrumented method handle, or null if logger not enabled 1459 */ 1460 public MethodHandle addLoggingToHandle(final Class<? extends Loggable> clazz, final Level level, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Supplier<String> text) { 1461 final DebugLogger log = getLogger(clazz); 1462 if (log.isEnabled()) { 1463 return MethodHandleFactory.addDebugPrintout(log, level, mh, paramStart, printReturnValue, text.get()); 1464 } 1465 return mh; 1466 } 1467 1468 private static String getLoggerName(final Class<?> clazz) { 1469 Class<?> current = clazz; 1470 while (current != null) { 1471 final Logger log = current.getAnnotation(Logger.class); 1472 if (log != null) { 1473 assert !"".equals(log.name()); 1474 return log.name(); 1475 } 1476 current = current.getSuperclass(); 1477 } 1478 assert false; 1479 return null; 1480 } 1481 1482 /** 1483 * This is a special kind of switchpoint used to guard builtin 1484 * properties and prototypes. In the future it might contain 1485 * logic to e.g. multiple switchpoint classes. 1486 */ 1487 public static final class BuiltinSwitchPoint extends SwitchPoint { 1488 //empty 1489 } 1490 1491 /** 1492 * Create a new builtin switchpoint and return it 1493 * @param name key name 1494 * @return new builtin switchpoint 1495 */ 1496 public SwitchPoint newBuiltinSwitchPoint(final String name) { 1497 assert builtinSwitchPoints.get(name) == null; 1498 final SwitchPoint sp = new BuiltinSwitchPoint(); 1499 builtinSwitchPoints.put(name, sp); 1500 return sp; 1501 } 1502 1503 /** 1504 * Return the builtin switchpoint for a particular key name 1505 * @param name key name 1506 * @return builtin switchpoint or null if none 1507 */ 1508 public SwitchPoint getBuiltinSwitchPoint(final String name) { 1509 return builtinSwitchPoints.get(name); 1510 } 1511 1512 }