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