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