Context.java revision 1096:ca0e54c45981
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 26package jdk.nashorn.internal.runtime; 27 28import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; 29import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION; 30import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; 31import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE; 32import static jdk.nashorn.internal.runtime.CodeStore.newCodeStore; 33import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 34import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 35import static jdk.nashorn.internal.runtime.Source.sourceFor; 36 37import java.io.File; 38import java.io.IOException; 39import java.io.PrintWriter; 40import java.lang.invoke.MethodHandle; 41import java.lang.invoke.MethodHandles; 42import java.lang.invoke.MethodType; 43import java.lang.invoke.SwitchPoint; 44import java.lang.ref.ReferenceQueue; 45import java.lang.ref.SoftReference; 46import java.lang.reflect.Field; 47import java.lang.reflect.Modifier; 48import java.net.MalformedURLException; 49import java.net.URL; 50import java.security.AccessControlContext; 51import java.security.AccessController; 52import java.security.CodeSigner; 53import java.security.CodeSource; 54import java.security.Permissions; 55import java.security.PrivilegedAction; 56import java.security.PrivilegedActionException; 57import java.security.PrivilegedExceptionAction; 58import java.security.ProtectionDomain; 59import java.util.Collection; 60import java.util.HashMap; 61import java.util.LinkedHashMap; 62import java.util.Map; 63import java.util.concurrent.atomic.AtomicLong; 64import java.util.concurrent.atomic.AtomicReference; 65import java.util.function.Consumer; 66import java.util.function.Supplier; 67import java.util.logging.Level; 68import javax.script.ScriptEngine; 69import jdk.internal.org.objectweb.asm.ClassReader; 70import jdk.internal.org.objectweb.asm.util.CheckClassAdapter; 71import jdk.nashorn.api.scripting.ClassFilter; 72import jdk.nashorn.api.scripting.ScriptObjectMirror; 73import jdk.nashorn.internal.codegen.Compiler; 74import jdk.nashorn.internal.codegen.Compiler.CompilationPhases; 75import jdk.nashorn.internal.codegen.ObjectClassGenerator; 76import jdk.nashorn.internal.ir.FunctionNode; 77import jdk.nashorn.internal.ir.debug.ASTWriter; 78import jdk.nashorn.internal.ir.debug.PrintVisitor; 79import jdk.nashorn.internal.lookup.MethodHandleFactory; 80import jdk.nashorn.internal.objects.Global; 81import jdk.nashorn.internal.parser.Parser; 82import jdk.nashorn.internal.runtime.events.RuntimeEvent; 83import jdk.nashorn.internal.runtime.logging.DebugLogger; 84import jdk.nashorn.internal.runtime.logging.Loggable; 85import jdk.nashorn.internal.runtime.logging.Logger; 86import jdk.nashorn.internal.runtime.options.LoggingOption.LoggerInfo; 87import jdk.nashorn.internal.runtime.options.Options; 88 89/** 90 * This class manages the global state of execution. Context is immutable. 91 */ 92public 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 try { 513 codeStore = newCodeStore(this); 514 } catch (final IOException e) { 515 throw new RuntimeException("Error initializing code cache", e); 516 } 517 } 518 519 // print version info if asked. 520 if (env._version) { 521 getErr().println("nashorn " + Version.version()); 522 } 523 524 if (env._fullversion) { 525 getErr().println("nashorn full version " + Version.fullVersion()); 526 } 527 528 initLoggers(); 529 } 530 531 532 /** 533 * Get the class filter for this context 534 * @return class filter 535 */ 536 public ClassFilter getClassFilter() { 537 return classFilter; 538 } 539 540 /** 541 * Returns the factory for constant method handles for global properties. The returned factory can be 542 * invalidated if this Context has more than one Global. 543 * @return the factory for constant method handles for global properties. 544 */ 545 GlobalConstants getGlobalConstants() { 546 return globalConstantsRef.get(); 547 } 548 549 /** 550 * Get the error manager for this context 551 * @return error manger 552 */ 553 public ErrorManager getErrorManager() { 554 return errors; 555 } 556 557 /** 558 * Get the script environment for this context 559 * @return script environment 560 */ 561 public ScriptEnvironment getEnv() { 562 return env; 563 } 564 565 /** 566 * Get the output stream for this context 567 * @return output print writer 568 */ 569 public PrintWriter getOut() { 570 return env.getOut(); 571 } 572 573 /** 574 * Get the error stream for this context 575 * @return error print writer 576 */ 577 public PrintWriter getErr() { 578 return env.getErr(); 579 } 580 581 /** 582 * Get the PropertyMap of the current global scope 583 * @return the property map of the current global scope 584 */ 585 public static PropertyMap getGlobalMap() { 586 return Context.getGlobal().getMap(); 587 } 588 589 /** 590 * Compile a top level script. 591 * 592 * @param source the source 593 * @param scope the scope 594 * 595 * @return top level function for script 596 */ 597 public ScriptFunction compileScript(final Source source, final ScriptObject scope) { 598 return compileScript(source, scope, this.errors); 599 } 600 601 /** 602 * Interface to represent compiled code that can be re-used across many 603 * global scope instances 604 */ 605 public static interface MultiGlobalCompiledScript { 606 /** 607 * Obtain script function object for a specific global scope object. 608 * 609 * @param newGlobal global scope for which function object is obtained 610 * @return script function for script level expressions 611 */ 612 public ScriptFunction getFunction(final Global newGlobal); 613 } 614 615 /** 616 * Compile a top level script. 617 * 618 * @param source the script source 619 * @return reusable compiled script across many global scopes. 620 */ 621 public MultiGlobalCompiledScript compileScript(final Source source) { 622 final Class<?> clazz = compile(source, this.errors, this._strict); 623 final MethodHandle createProgramFunctionHandle = getCreateProgramFunctionHandle(clazz); 624 625 return new MultiGlobalCompiledScript() { 626 @Override 627 public ScriptFunction getFunction(final Global newGlobal) { 628 return invokeCreateProgramFunctionHandle(createProgramFunctionHandle, newGlobal); 629 } 630 }; 631 } 632 633 /** 634 * Entry point for {@code eval} 635 * 636 * @param initialScope The scope of this eval call 637 * @param string Evaluated code as a String 638 * @param callThis "this" to be passed to the evaluated code 639 * @param location location of the eval call 640 * @param strict is this {@code eval} call from a strict mode code? 641 * @return the return value of the {@code eval} 642 */ 643 public Object eval(final ScriptObject initialScope, final String string, 644 final Object callThis, final Object location, final boolean strict) { 645 return eval(initialScope, string, callThis, location, strict, false); 646 } 647 648 /** 649 * Entry point for {@code eval} 650 * 651 * @param initialScope The scope of this eval call 652 * @param string Evaluated code as a String 653 * @param callThis "this" to be passed to the evaluated code 654 * @param location location of the eval call 655 * @param strict is this {@code eval} call from a strict mode code? 656 * @param evalCall is this called from "eval" builtin? 657 * 658 * @return the return value of the {@code eval} 659 */ 660 public Object eval(final ScriptObject initialScope, final String string, 661 final Object callThis, final Object location, final boolean strict, final boolean evalCall) { 662 final String file = location == UNDEFINED || location == null ? "<eval>" : location.toString(); 663 final Source source = sourceFor(file, string, evalCall); 664 final boolean directEval = location != UNDEFINED; // is this direct 'eval' call or indirectly invoked eval? 665 final Global global = Context.getGlobal(); 666 ScriptObject scope = initialScope; 667 668 // ECMA section 10.1.1 point 2 says eval code is strict if it begins 669 // with "use strict" directive or eval direct call itself is made 670 // from from strict mode code. We are passed with caller's strict mode. 671 boolean strictFlag = directEval && strict; 672 673 Class<?> clazz = null; 674 try { 675 clazz = compile(source, new ThrowErrorManager(), strictFlag); 676 } catch (final ParserException e) { 677 e.throwAsEcmaException(global); 678 return null; 679 } 680 681 if (!strictFlag) { 682 // We need to get strict mode flag from compiled class. This is 683 // because eval code may start with "use strict" directive. 684 try { 685 strictFlag = clazz.getField(STRICT_MODE.symbolName()).getBoolean(null); 686 } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { 687 //ignored 688 strictFlag = false; 689 } 690 } 691 692 // In strict mode, eval does not instantiate variables and functions 693 // in the caller's environment. A new environment is created! 694 if (strictFlag) { 695 // Create a new scope object 696 final ScriptObject strictEvalScope = global.newObject(); 697 698 // bless it as a "scope" 699 strictEvalScope.setIsScope(); 700 701 // set given scope to be it's proto so that eval can still 702 // access caller environment vars in the new environment. 703 strictEvalScope.setProto(scope); 704 scope = strictEvalScope; 705 } 706 707 final ScriptFunction func = getProgramFunction(clazz, scope); 708 Object evalThis; 709 if (directEval) { 710 evalThis = callThis instanceof ScriptObject || strictFlag ? callThis : global; 711 } else { 712 evalThis = global; 713 } 714 715 return ScriptRuntime.apply(func, evalThis); 716 } 717 718 private static Source loadInternal(final String srcStr, final String prefix, final String resourcePath) { 719 if (srcStr.startsWith(prefix)) { 720 final String resource = resourcePath + srcStr.substring(prefix.length()); 721 // NOTE: even sandbox scripts should be able to load scripts in nashorn: scheme 722 // These scripts are always available and are loaded from nashorn.jar's resources. 723 return AccessController.doPrivileged( 724 new PrivilegedAction<Source>() { 725 @Override 726 public Source run() { 727 try { 728 final URL resURL = Context.class.getResource(resource); 729 return resURL != null ? sourceFor(srcStr, resURL) : null; 730 } catch (final IOException exp) { 731 return null; 732 } 733 } 734 }); 735 } 736 737 return null; 738 } 739 740 /** 741 * Implementation of {@code load} Nashorn extension. Load a script file from a source 742 * expression 743 * 744 * @param scope the scope 745 * @param from source expression for script 746 * 747 * @return return value for load call (undefined) 748 * 749 * @throws IOException if source cannot be found or loaded 750 */ 751 public Object load(final ScriptObject scope, final Object from) throws IOException { 752 final Object src = from instanceof ConsString ? from.toString() : from; 753 Source source = null; 754 755 // load accepts a String (which could be a URL or a file name), a File, a URL 756 // or a ScriptObject that has "name" and "source" (string valued) properties. 757 if (src instanceof String) { 758 final String srcStr = (String)src; 759 if (srcStr.startsWith(LOAD_CLASSPATH)) { 760 final URL url = getResourceURL(srcStr.substring(LOAD_CLASSPATH.length())); 761 source = url != null ? sourceFor(url.toString(), url) : null; 762 } else { 763 final File file = new File(srcStr); 764 if (srcStr.indexOf(':') != -1) { 765 if ((source = loadInternal(srcStr, LOAD_NASHORN, "resources/")) == null && 766 (source = loadInternal(srcStr, LOAD_FX, "resources/fx/")) == null) { 767 URL url; 768 try { 769 //check for malformed url. if malformed, it may still be a valid file 770 url = new URL(srcStr); 771 } catch (final MalformedURLException e) { 772 url = file.toURI().toURL(); 773 } 774 source = sourceFor(url.toString(), url); 775 } 776 } else if (file.isFile()) { 777 source = sourceFor(srcStr, file); 778 } 779 } 780 } else if (src instanceof File && ((File)src).isFile()) { 781 final File file = (File)src; 782 source = sourceFor(file.getName(), file); 783 } else if (src instanceof URL) { 784 final URL url = (URL)src; 785 source = sourceFor(url.toString(), url); 786 } else if (src instanceof ScriptObject) { 787 final ScriptObject sobj = (ScriptObject)src; 788 if (sobj.has("script") && sobj.has("name")) { 789 final String script = JSType.toString(sobj.get("script")); 790 final String name = JSType.toString(sobj.get("name")); 791 source = sourceFor(name, script); 792 } 793 } else if (src instanceof Map) { 794 final Map<?,?> map = (Map<?,?>)src; 795 if (map.containsKey("script") && map.containsKey("name")) { 796 final String script = JSType.toString(map.get("script")); 797 final String name = JSType.toString(map.get("name")); 798 source = sourceFor(name, script); 799 } 800 } 801 802 if (source != null) { 803 return evaluateSource(source, scope, scope); 804 } 805 806 throw typeError("cant.load.script", ScriptRuntime.safeToString(from)); 807 } 808 809 /** 810 * Implementation of {@code loadWithNewGlobal} Nashorn extension. Load a script file from a source 811 * expression, after creating a new global scope. 812 * 813 * @param from source expression for script 814 * @param args (optional) arguments to be passed to the loaded script 815 * 816 * @return return value for load call (undefined) 817 * 818 * @throws IOException if source cannot be found or loaded 819 */ 820 public Object loadWithNewGlobal(final Object from, final Object...args) throws IOException { 821 final Global oldGlobal = getGlobal(); 822 final Global newGlobal = AccessController.doPrivileged(new PrivilegedAction<Global>() { 823 @Override 824 public Global run() { 825 try { 826 return newGlobal(); 827 } catch (final RuntimeException e) { 828 if (Context.DEBUG) { 829 e.printStackTrace(); 830 } 831 throw e; 832 } 833 } 834 }, CREATE_GLOBAL_ACC_CTXT); 835 // initialize newly created Global instance 836 initGlobal(newGlobal); 837 setGlobal(newGlobal); 838 839 final Object[] wrapped = args == null? ScriptRuntime.EMPTY_ARRAY : ScriptObjectMirror.wrapArray(args, oldGlobal); 840 newGlobal.put("arguments", newGlobal.wrapAsObject(wrapped), env._strict); 841 842 try { 843 // wrap objects from newGlobal's world as mirrors - but if result 844 // is from oldGlobal's world, unwrap it! 845 return ScriptObjectMirror.unwrap(ScriptObjectMirror.wrap(load(newGlobal, from), newGlobal), oldGlobal); 846 } finally { 847 setGlobal(oldGlobal); 848 } 849 } 850 851 /** 852 * Load or get a structure class. Structure class names are based on the number of parameter fields 853 * and {@link AccessorProperty} fields in them. Structure classes are used to represent ScriptObjects 854 * 855 * @see ObjectClassGenerator 856 * @see AccessorProperty 857 * @see ScriptObject 858 * 859 * @param fullName full name of class, e.g. jdk.nashorn.internal.objects.JO2P1 contains 2 fields and 1 parameter. 860 * 861 * @return the {@code Class<?>} for this structure 862 * 863 * @throws ClassNotFoundException if structure class cannot be resolved 864 */ 865 @SuppressWarnings("unchecked") 866 public static Class<? extends ScriptObject> forStructureClass(final String fullName) throws ClassNotFoundException { 867 if (System.getSecurityManager() != null && !StructureLoader.isStructureClass(fullName)) { 868 throw new ClassNotFoundException(fullName); 869 } 870 return (Class<? extends ScriptObject>)Class.forName(fullName, true, sharedLoader); 871 } 872 873 /** 874 * Checks that the given Class can be accessed from no permissions context. 875 * 876 * @param clazz Class object 877 * @throws SecurityException if not accessible 878 */ 879 public static void checkPackageAccess(final Class<?> clazz) { 880 final SecurityManager sm = System.getSecurityManager(); 881 if (sm != null) { 882 Class<?> bottomClazz = clazz; 883 while (bottomClazz.isArray()) { 884 bottomClazz = bottomClazz.getComponentType(); 885 } 886 checkPackageAccess(sm, bottomClazz.getName()); 887 } 888 } 889 890 /** 891 * Checks that the given package name can be accessed from no permissions context. 892 * 893 * @param pkgName package name 894 * @throws SecurityException if not accessible 895 */ 896 public static void checkPackageAccess(final String pkgName) { 897 final SecurityManager sm = System.getSecurityManager(); 898 if (sm != null) { 899 checkPackageAccess(sm, pkgName.endsWith(".") ? pkgName : pkgName + "."); 900 } 901 } 902 903 /** 904 * Checks that the given package can be accessed from no permissions context. 905 * 906 * @param sm current security manager instance 907 * @param fullName fully qualified package name 908 * @throw SecurityException if not accessible 909 */ 910 private static void checkPackageAccess(final SecurityManager sm, final String fullName) { 911 sm.getClass(); // null check 912 final int index = fullName.lastIndexOf('.'); 913 if (index != -1) { 914 final String pkgName = fullName.substring(0, index); 915 AccessController.doPrivileged(new PrivilegedAction<Void>() { 916 @Override 917 public Void run() { 918 sm.checkPackageAccess(pkgName); 919 return null; 920 } 921 }, NO_PERMISSIONS_ACC_CTXT); 922 } 923 } 924 925 /** 926 * Checks that the given Class can be accessed from no permissions context. 927 * 928 * @param clazz Class object 929 * @return true if package is accessible, false otherwise 930 */ 931 private static boolean isAccessiblePackage(final Class<?> clazz) { 932 try { 933 checkPackageAccess(clazz); 934 return true; 935 } catch (final SecurityException se) { 936 return false; 937 } 938 } 939 940 /** 941 * Checks that the given Class is public and it can be accessed from no permissions context. 942 * 943 * @param clazz Class object to check 944 * @return true if Class is accessible, false otherwise 945 */ 946 public static boolean isAccessibleClass(final Class<?> clazz) { 947 return Modifier.isPublic(clazz.getModifiers()) && Context.isAccessiblePackage(clazz); 948 } 949 950 /** 951 * Lookup a Java class. This is used for JSR-223 stuff linking in from 952 * {@code jdk.nashorn.internal.objects.NativeJava} and {@code jdk.nashorn.internal.runtime.NativeJavaPackage} 953 * 954 * @param fullName full name of class to load 955 * 956 * @return the {@code Class<?>} for the name 957 * 958 * @throws ClassNotFoundException if class cannot be resolved 959 */ 960 public Class<?> findClass(final String fullName) throws ClassNotFoundException { 961 if (fullName.indexOf('[') != -1 || fullName.indexOf('/') != -1) { 962 // don't allow array class names or internal names. 963 throw new ClassNotFoundException(fullName); 964 } 965 966 // give chance to ClassFilter to filter out, if present 967 if (classFilter != null && !classFilter.exposeToScripts(fullName)) { 968 throw new ClassNotFoundException(fullName); 969 } 970 971 // check package access as soon as possible! 972 final SecurityManager sm = System.getSecurityManager(); 973 if (sm != null) { 974 checkPackageAccess(sm, fullName); 975 } 976 977 // try the script -classpath loader, if that is set 978 if (classPathLoader != null) { 979 try { 980 return Class.forName(fullName, true, classPathLoader); 981 } catch (final ClassNotFoundException ignored) { 982 // ignore, continue search 983 } 984 } 985 986 // Try finding using the "app" loader. 987 return Class.forName(fullName, true, appLoader); 988 } 989 990 /** 991 * Hook to print stack trace for a {@link Throwable} that occurred during 992 * execution 993 * 994 * @param t throwable for which to dump stack 995 */ 996 public static void printStackTrace(final Throwable t) { 997 if (Context.DEBUG) { 998 t.printStackTrace(Context.getCurrentErr()); 999 } 1000 } 1001 1002 /** 1003 * Verify generated bytecode before emission. This is called back from the 1004 * {@link ObjectClassGenerator} or the {@link Compiler}. If the "--verify-code" parameter 1005 * hasn't been given, this is a nop 1006 * 1007 * Note that verification may load classes -- we don't want to do that unless 1008 * user specified verify option. We check it here even though caller 1009 * may have already checked that flag 1010 * 1011 * @param bytecode bytecode to verify 1012 */ 1013 public void verify(final byte[] bytecode) { 1014 if (env._verify_code) { 1015 // No verification when security manager is around as verifier 1016 // may load further classes - which should be avoided. 1017 if (System.getSecurityManager() == null) { 1018 CheckClassAdapter.verify(new ClassReader(bytecode), sharedLoader, false, new PrintWriter(System.err, true)); 1019 } 1020 } 1021 } 1022 1023 /** 1024 * Create and initialize a new global scope object. 1025 * 1026 * @return the initialized global scope object. 1027 */ 1028 public Global createGlobal() { 1029 return initGlobal(newGlobal()); 1030 } 1031 1032 /** 1033 * Create a new uninitialized global scope object 1034 * @return the global script object 1035 */ 1036 public Global newGlobal() { 1037 createOrInvalidateGlobalConstants(); 1038 return new Global(this); 1039 } 1040 1041 private void createOrInvalidateGlobalConstants() { 1042 for (;;) { 1043 final GlobalConstants currentGlobalConstants = getGlobalConstants(); 1044 if (currentGlobalConstants != null) { 1045 // Subsequent invocation; we're creating our second or later Global. GlobalConstants is not safe to use 1046 // with more than one Global, as the constant method handle linkages it creates create a coupling 1047 // between the Global and the call sites in the compiled code. 1048 currentGlobalConstants.invalidateForever(); 1049 return; 1050 } 1051 final GlobalConstants newGlobalConstants = new GlobalConstants(getLogger(GlobalConstants.class)); 1052 if (globalConstantsRef.compareAndSet(null, newGlobalConstants)) { 1053 // First invocation; we're creating the first Global in this Context. Create the GlobalConstants object 1054 // for this Context. 1055 return; 1056 } 1057 1058 // If we reach here, then we started out as the first invocation, but another concurrent invocation won the 1059 // CAS race. We'll just let the loop repeat and invalidate the CAS race winner. 1060 } 1061 } 1062 1063 /** 1064 * Initialize given global scope object. 1065 * 1066 * @param global the global 1067 * @param engine the associated ScriptEngine instance, can be null 1068 * @return the initialized global scope object. 1069 */ 1070 public Global initGlobal(final Global global, final ScriptEngine engine) { 1071 // Need only minimal global object, if we are just compiling. 1072 if (!env._compile_only) { 1073 final Global oldGlobal = Context.getGlobal(); 1074 try { 1075 Context.setGlobal(global); 1076 // initialize global scope with builtin global objects 1077 global.initBuiltinObjects(engine); 1078 } finally { 1079 Context.setGlobal(oldGlobal); 1080 } 1081 } 1082 1083 return global; 1084 } 1085 1086 /** 1087 * Initialize given global scope object. 1088 * 1089 * @param global the global 1090 * @return the initialized global scope object. 1091 */ 1092 public Global initGlobal(final Global global) { 1093 return initGlobal(global, null); 1094 } 1095 1096 /** 1097 * Return the current global's context 1098 * @return current global's context 1099 */ 1100 static Context getContextTrusted() { 1101 return getContext(getGlobal()); 1102 } 1103 1104 static Context getContextTrustedOrNull() { 1105 final Global global = Context.getGlobal(); 1106 return global == null ? null : getContext(global); 1107 } 1108 1109 private static Context getContext(final Global global) { 1110 // We can't invoke Global.getContext() directly, as it's a protected override, and Global isn't in our package. 1111 // In order to access the method, we must cast it to ScriptObject first (which is in our package) and then let 1112 // virtual invocation do its thing. 1113 return ((ScriptObject)global).getContext(); 1114 } 1115 1116 /** 1117 * Try to infer Context instance from the Class. If we cannot, 1118 * then get it from the thread local variable. 1119 * 1120 * @param clazz the class 1121 * @return context 1122 */ 1123 static Context fromClass(final Class<?> clazz) { 1124 final ClassLoader loader = clazz.getClassLoader(); 1125 1126 if (loader instanceof ScriptLoader) { 1127 return ((ScriptLoader)loader).getContext(); 1128 } 1129 1130 return Context.getContextTrusted(); 1131 } 1132 1133 private URL getResourceURL(final String resName) { 1134 // try the classPathLoader if we have and then 1135 // try the appLoader if non-null. 1136 if (classPathLoader != null) { 1137 return classPathLoader.getResource(resName); 1138 } else if (appLoader != null) { 1139 return appLoader.getResource(resName); 1140 } 1141 1142 return null; 1143 } 1144 1145 private Object evaluateSource(final Source source, final ScriptObject scope, final ScriptObject thiz) { 1146 ScriptFunction script = null; 1147 1148 try { 1149 script = compileScript(source, scope, new Context.ThrowErrorManager()); 1150 } catch (final ParserException e) { 1151 e.throwAsEcmaException(); 1152 } 1153 1154 return ScriptRuntime.apply(script, thiz); 1155 } 1156 1157 private static ScriptFunction getProgramFunction(final Class<?> script, final ScriptObject scope) { 1158 if (script == null) { 1159 return null; 1160 } 1161 return invokeCreateProgramFunctionHandle(getCreateProgramFunctionHandle(script), scope); 1162 } 1163 1164 private static MethodHandle getCreateProgramFunctionHandle(final Class<?> script) { 1165 try { 1166 return LOOKUP.findStatic(script, CREATE_PROGRAM_FUNCTION.symbolName(), CREATE_PROGRAM_FUNCTION_TYPE); 1167 } catch (NoSuchMethodException | IllegalAccessException e) { 1168 throw new AssertionError("Failed to retrieve a handle for the program function for " + script.getName(), e); 1169 } 1170 } 1171 1172 private static ScriptFunction invokeCreateProgramFunctionHandle(final MethodHandle createProgramFunctionHandle, final ScriptObject scope) { 1173 try { 1174 return (ScriptFunction)createProgramFunctionHandle.invokeExact(scope); 1175 } catch (final RuntimeException|Error e) { 1176 throw e; 1177 } catch (final Throwable t) { 1178 throw new AssertionError("Failed to create a program function", t); 1179 } 1180 } 1181 1182 private ScriptFunction compileScript(final Source source, final ScriptObject scope, final ErrorManager errMan) { 1183 return getProgramFunction(compile(source, errMan, this._strict), scope); 1184 } 1185 1186 private synchronized Class<?> compile(final Source source, final ErrorManager errMan, final boolean strict) { 1187 // start with no errors, no warnings. 1188 errMan.reset(); 1189 1190 Class<?> script = findCachedClass(source); 1191 if (script != null) { 1192 final DebugLogger log = getLogger(Compiler.class); 1193 if (log.isEnabled()) { 1194 log.fine(new RuntimeEvent<>(Level.INFO, source), "Code cache hit for ", source, " avoiding recompile."); 1195 } 1196 return script; 1197 } 1198 1199 StoredScript storedScript = null; 1200 FunctionNode functionNode = null; 1201 // We only use the code store here if optimistic types are disabled. With optimistic types, initial compilation 1202 // just creates a thin wrapper, and actual code is stored per function in RecompilableScriptFunctionData. 1203 final boolean useCodeStore = env._persistent_cache && !env._parse_only && !env._optimistic_types; 1204 final String cacheKey = useCodeStore ? CodeStore.getCacheKey(0, null) : null; 1205 1206 if (useCodeStore) { 1207 storedScript = codeStore.load(source, cacheKey); 1208 } 1209 1210 if (storedScript == null) { 1211 functionNode = new Parser(env, source, errMan, strict, getLogger(Parser.class)).parse(); 1212 1213 if (errMan.hasErrors()) { 1214 return null; 1215 } 1216 1217 if (env._print_ast || functionNode.getFlag(FunctionNode.IS_PRINT_AST)) { 1218 getErr().println(new ASTWriter(functionNode)); 1219 } 1220 1221 if (env._print_parse || functionNode.getFlag(FunctionNode.IS_PRINT_PARSE)) { 1222 getErr().println(new PrintVisitor(functionNode, true, false)); 1223 } 1224 } 1225 1226 if (env._parse_only) { 1227 return null; 1228 } 1229 1230 final URL url = source.getURL(); 1231 final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader; 1232 final CodeSource cs = new CodeSource(url, (CodeSigner[])null); 1233 final CodeInstaller<ScriptEnvironment> installer = new ContextCodeInstaller(this, loader, cs); 1234 1235 if (storedScript == null) { 1236 final CompilationPhases phases = Compiler.CompilationPhases.COMPILE_ALL; 1237 1238 final Compiler compiler = new Compiler( 1239 this, 1240 env, 1241 installer, 1242 source, 1243 errMan, 1244 strict | functionNode.isStrict()); 1245 1246 final FunctionNode compiledFunction = compiler.compile(functionNode, phases); 1247 if (errMan.hasErrors()) { 1248 return null; 1249 } 1250 script = compiledFunction.getRootClass(); 1251 compiler.persistClassInfo(cacheKey, compiledFunction); 1252 } else { 1253 Compiler.updateCompilationId(storedScript.getCompilationId()); 1254 script = install(storedScript, source, installer); 1255 } 1256 1257 cacheClass(source, script); 1258 return script; 1259 } 1260 1261 private ScriptLoader createNewLoader() { 1262 return AccessController.doPrivileged( 1263 new PrivilegedAction<ScriptLoader>() { 1264 @Override 1265 public ScriptLoader run() { 1266 return new ScriptLoader(appLoader, Context.this); 1267 } 1268 }, CREATE_LOADER_ACC_CTXT); 1269 } 1270 1271 private long getUniqueScriptId() { 1272 return uniqueScriptId.getAndIncrement(); 1273 } 1274 1275 /** 1276 * Install a previously compiled class from the code cache. 1277 * 1278 * @param storedScript cached script containing class bytes and constants 1279 * @return main script class 1280 */ 1281 private static Class<?> install(final StoredScript storedScript, final Source source, final CodeInstaller<ScriptEnvironment> installer) { 1282 1283 final Map<String, Class<?>> installedClasses = new HashMap<>(); 1284 final Map<String, byte[]> classBytes = storedScript.getClassBytes(); 1285 final Object[] constants = storedScript.getConstants(); 1286 final String mainClassName = storedScript.getMainClassName(); 1287 final byte[] mainClassBytes = classBytes.get(mainClassName); 1288 final Class<?> mainClass = installer.install(mainClassName, mainClassBytes); 1289 final Map<Integer, FunctionInitializer> initializers = storedScript.getInitializers(); 1290 1291 installedClasses.put(mainClassName, mainClass); 1292 1293 for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) { 1294 final String className = entry.getKey(); 1295 if (className.equals(mainClassName)) { 1296 continue; 1297 } 1298 final byte[] code = entry.getValue(); 1299 1300 installedClasses.put(className, installer.install(className, code)); 1301 } 1302 1303 installer.initialize(installedClasses.values(), source, constants); 1304 1305 for (final Object constant : constants) { 1306 if (constant instanceof RecompilableScriptFunctionData) { 1307 final RecompilableScriptFunctionData data = (RecompilableScriptFunctionData) constant; 1308 data.initTransients(source, installer); 1309 final FunctionInitializer initializer = initializers.get(data.getFunctionNodeId()); 1310 if (initializer != null) { 1311 initializer.setCode(installedClasses.get(initializer.getClassName())); 1312 data.initializeCode(initializer); 1313 } 1314 } 1315 } 1316 1317 return mainClass; 1318 } 1319 1320 /** 1321 * Cache for compiled script classes. 1322 */ 1323 @SuppressWarnings("serial") 1324 @Logger(name="classcache") 1325 private static class ClassCache extends LinkedHashMap<Source, ClassReference> implements Loggable { 1326 private final int size; 1327 private final ReferenceQueue<Class<?>> queue; 1328 private final DebugLogger log; 1329 1330 ClassCache(final Context context, final int size) { 1331 super(size, 0.75f, true); 1332 this.size = size; 1333 this.queue = new ReferenceQueue<>(); 1334 this.log = initLogger(context); 1335 } 1336 1337 void cache(final Source source, final Class<?> clazz) { 1338 if (log.isEnabled()) { 1339 log.info("Caching ", source, " in class cache"); 1340 } 1341 put(source, new ClassReference(clazz, queue, source)); 1342 } 1343 1344 @Override 1345 protected boolean removeEldestEntry(final Map.Entry<Source, ClassReference> eldest) { 1346 return size() > size; 1347 } 1348 1349 @Override 1350 public ClassReference get(final Object key) { 1351 for (ClassReference ref; (ref = (ClassReference)queue.poll()) != null; ) { 1352 final Source source = ref.source; 1353 if (log.isEnabled()) { 1354 log.info("Evicting ", source, " from class cache."); 1355 } 1356 remove(source); 1357 } 1358 1359 final ClassReference ref = super.get(key); 1360 if (ref != null && log.isEnabled()) { 1361 log.info("Retrieved class reference for ", ref.source, " from class cache"); 1362 } 1363 return ref; 1364 } 1365 1366 @Override 1367 public DebugLogger initLogger(final Context context) { 1368 return context.getLogger(getClass()); 1369 } 1370 1371 @Override 1372 public DebugLogger getLogger() { 1373 return log; 1374 } 1375 1376 } 1377 1378 private static class ClassReference extends SoftReference<Class<?>> { 1379 private final Source source; 1380 1381 ClassReference(final Class<?> clazz, final ReferenceQueue<Class<?>> queue, final Source source) { 1382 super(clazz, queue); 1383 this.source = source; 1384 } 1385 } 1386 1387 // Class cache management 1388 private Class<?> findCachedClass(final Source source) { 1389 final ClassReference ref = classCache == null ? null : classCache.get(source); 1390 return ref != null ? ref.get() : null; 1391 } 1392 1393 private void cacheClass(final Source source, final Class<?> clazz) { 1394 if (classCache != null) { 1395 classCache.cache(source, clazz); 1396 } 1397 } 1398 1399 // logging 1400 private final Map<String, DebugLogger> loggers = new HashMap<>(); 1401 1402 private void initLoggers() { 1403 ((Loggable)MethodHandleFactory.getFunctionality()).initLogger(this); 1404 } 1405 1406 /** 1407 * Get a logger, given a loggable class 1408 * @param clazz a Loggable class 1409 * @return debuglogger associated with that class 1410 */ 1411 public DebugLogger getLogger(final Class<? extends Loggable> clazz) { 1412 return getLogger(clazz, null); 1413 } 1414 1415 /** 1416 * Get a logger, given a loggable class 1417 * @param clazz a Loggable class 1418 * @param initHook an init hook - if this is the first time the logger is created in the context, run the init hook 1419 * @return debuglogger associated with that class 1420 */ 1421 public DebugLogger getLogger(final Class<? extends Loggable> clazz, final Consumer<DebugLogger> initHook) { 1422 final String name = getLoggerName(clazz); 1423 DebugLogger logger = loggers.get(name); 1424 if (logger == null) { 1425 if (!env.hasLogger(name)) { 1426 return DebugLogger.DISABLED_LOGGER; 1427 } 1428 final LoggerInfo info = env._loggers.get(name); 1429 logger = new DebugLogger(name, info.getLevel(), info.isQuiet()); 1430 if (initHook != null) { 1431 initHook.accept(logger); 1432 } 1433 loggers.put(name, logger); 1434 } 1435 return logger; 1436 } 1437 1438 /** 1439 * Given a Loggable class, weave debug info info a method handle for that logger. 1440 * Level.INFO is used 1441 * 1442 * @param clazz loggable 1443 * @param mh method handle 1444 * @param text debug printout to add 1445 * 1446 * @return instrumented method handle, or null if logger not enabled 1447 */ 1448 public MethodHandle addLoggingToHandle(final Class<? extends Loggable> clazz, final MethodHandle mh, final Supplier<String> text) { 1449 return addLoggingToHandle(clazz, Level.INFO, mh, Integer.MAX_VALUE, false, text); 1450 } 1451 1452 /** 1453 * Given a Loggable class, weave debug info info a method handle for that logger. 1454 * 1455 * @param clazz loggable 1456 * @param level log level 1457 * @param mh method handle 1458 * @param paramStart first parameter to print 1459 * @param printReturnValue should we print the return vaulue? 1460 * @param text debug printout to add 1461 * 1462 * @return instrumented method handle, or null if logger not enabled 1463 */ 1464 public MethodHandle addLoggingToHandle(final Class<? extends Loggable> clazz, final Level level, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Supplier<String> text) { 1465 final DebugLogger log = getLogger(clazz); 1466 if (log.isEnabled()) { 1467 return MethodHandleFactory.addDebugPrintout(log, level, mh, paramStart, printReturnValue, text.get()); 1468 } 1469 return mh; 1470 } 1471 1472 private static String getLoggerName(final Class<?> clazz) { 1473 Class<?> current = clazz; 1474 while (current != null) { 1475 final Logger log = current.getAnnotation(Logger.class); 1476 if (log != null) { 1477 assert !"".equals(log.name()); 1478 return log.name(); 1479 } 1480 current = current.getSuperclass(); 1481 } 1482 assert false; 1483 return null; 1484 } 1485 1486 /** 1487 * This is a special kind of switchpoint used to guard builtin 1488 * properties and prototypes. In the future it might contain 1489 * logic to e.g. multiple switchpoint classes. 1490 */ 1491 public static final class BuiltinSwitchPoint extends SwitchPoint { 1492 //empty 1493 } 1494 1495 /** 1496 * Create a new builtin switchpoint and return it 1497 * @param name key name 1498 * @return new builtin switchpoint 1499 */ 1500 public SwitchPoint newBuiltinSwitchPoint(final String name) { 1501 assert builtinSwitchPoints.get(name) == null; 1502 final SwitchPoint sp = new BuiltinSwitchPoint(); 1503 builtinSwitchPoints.put(name, sp); 1504 return sp; 1505 } 1506 1507 /** 1508 * Return the builtin switchpoint for a particular key name 1509 * @param name key name 1510 * @return builtin switchpoint or null if none 1511 */ 1512 public SwitchPoint getBuiltinSwitchPoint(final String name) { 1513 return builtinSwitchPoints.get(name); 1514 } 1515 1516} 1517