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