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