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