/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.runtime;

import static jdk.internal.org.objectweb.asm.Opcodes.V1_7;
import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION;
import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE;
import static jdk.nashorn.internal.runtime.CodeStore.newCodeStore;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import static jdk.nashorn.internal.runtime.Source.sourceFor;

import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.SwitchPoint;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.script.ScriptEngine;
import jdk.dynalink.DynamicLinker;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.internal.WeakValueCache;
import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.debug.ASTWriter;
import jdk.nashorn.internal.ir.debug.PrintVisitor;
import jdk.nashorn.internal.lookup.MethodHandleFactory;
import jdk.nashorn.internal.objects.Global;
import jdk.nashorn.internal.parser.Parser;
import jdk.nashorn.internal.runtime.events.RuntimeEvent;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
import jdk.nashorn.internal.runtime.logging.Loggable;
import jdk.nashorn.internal.runtime.logging.Logger;
import jdk.nashorn.internal.runtime.options.LoggingOption.LoggerInfo;
import jdk.nashorn.internal.runtime.options.Options;
import jdk.internal.misc.Unsafe;

This class manages the global state of execution. Context is immutable.
/** * This class manages the global state of execution. Context is immutable. */
public final class Context { // nashorn specific security runtime access permission names
Permission needed to pass arbitrary nashorn command line options when creating Context.
/** * Permission needed to pass arbitrary nashorn command line options when creating Context. */
public static final String NASHORN_SET_CONFIG = "nashorn.setConfig";
Permission needed to create Nashorn Context instance.
/** * Permission needed to create Nashorn Context instance. */
public static final String NASHORN_CREATE_CONTEXT = "nashorn.createContext";
Permission needed to create Nashorn Global instance.
/** * Permission needed to create Nashorn Global instance. */
public static final String NASHORN_CREATE_GLOBAL = "nashorn.createGlobal";
Permission to get current Nashorn Context from thread local storage.
/** * Permission to get current Nashorn Context from thread local storage. */
public static final String NASHORN_GET_CONTEXT = "nashorn.getContext";
Permission to use Java reflection/jsr292 from script code.
/** * Permission to use Java reflection/jsr292 from script code. */
public static final String NASHORN_JAVA_REFLECTION = "nashorn.JavaReflection";
Permission to enable nashorn debug mode.
/** * Permission to enable nashorn debug mode. */
public static final String NASHORN_DEBUG_MODE = "nashorn.debugMode"; // nashorn load psuedo URL prefixes private static final String LOAD_CLASSPATH = "classpath:"; private static final String LOAD_FX = "fx:"; private static final String LOAD_NASHORN = "nashorn:"; private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final MethodType CREATE_PROGRAM_FUNCTION_TYPE = MethodType.methodType(ScriptFunction.class, ScriptObject.class); private static final LongAdder NAMED_INSTALLED_SCRIPT_COUNT = new LongAdder(); private static final LongAdder ANONYMOUS_INSTALLED_SCRIPT_COUNT = new LongAdder();
Should scripts use only object slots for fields, or dual long/object slots? The default behaviour is to couple this to optimistic types, using dual representation if optimistic types are enabled and single field representation otherwise. This can be overridden by setting either the "nashorn.fields.objects" or "nashorn.fields.dual" system property.
/** * Should scripts use only object slots for fields, or dual long/object slots? The default * behaviour is to couple this to optimistic types, using dual representation if optimistic types are enabled * and single field representation otherwise. This can be overridden by setting either the "nashorn.fields.objects" * or "nashorn.fields.dual" system property. */
private final FieldMode fieldMode; private static enum FieldMode {
Value for automatic field representation depending on optimistic types setting
/** Value for automatic field representation depending on optimistic types setting */
AUTO,
Value for object field representation regardless of optimistic types setting
/** Value for object field representation regardless of optimistic types setting */
OBJECTS,
Value for dual primitive/object field representation regardless of optimistic types setting
/** Value for dual primitive/object field representation regardless of optimistic types setting */
DUAL }
Keeps track of which builtin prototypes and properties have been relinked Currently we are conservative and associate the name of a builtin class with all its properties, so it's enough to invalidate a property to break all assumptions about a prototype. This can be changed to a more fine grained approach, but no one ever needs this, given the very rare occurrence of swapping out only parts of a builtin v.s. the entire builtin object
/** * Keeps track of which builtin prototypes and properties have been relinked * Currently we are conservative and associate the name of a builtin class with all * its properties, so it's enough to invalidate a property to break all assumptions * about a prototype. This can be changed to a more fine grained approach, but no one * ever needs this, given the very rare occurrence of swapping out only parts of * a builtin v.s. the entire builtin object */
private final Map<String, SwitchPoint> builtinSwitchPoints = new HashMap<>(); /* Force DebuggerSupport to be loaded. */ static { DebuggerSupport.FORCELOAD = true; } static long getNamedInstalledScriptCount() { return NAMED_INSTALLED_SCRIPT_COUNT.sum(); } static long getAnonymousInstalledScriptCount() { return ANONYMOUS_INSTALLED_SCRIPT_COUNT.sum(); }
ContextCodeInstaller that has the privilege of installing classes in the Context. Can only be instantiated from inside the context and is opaque to other classes
/** * ContextCodeInstaller that has the privilege of installing classes in the Context. * Can only be instantiated from inside the context and is opaque to other classes */
private abstract static class ContextCodeInstaller implements CodeInstaller { final Context context; final CodeSource codeSource; ContextCodeInstaller(final Context context, final CodeSource codeSource) { this.context = context; this.codeSource = codeSource; } @Override public Context getContext() { return context; } @Override public void initialize(final Collection<Class<?>> classes, final Source source, final Object[] constants) { try { AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { @Override public Void run() throws Exception { for (final Class<?> clazz : classes) { //use reflection to write source and constants table to installed classes final Field sourceField = clazz.getDeclaredField(SOURCE.symbolName()); sourceField.setAccessible(true); sourceField.set(null, source); final Field constantsField = clazz.getDeclaredField(CONSTANTS.symbolName()); constantsField.setAccessible(true); constantsField.set(null, constants); } return null; } }); } catch (final PrivilegedActionException e) { throw new RuntimeException(e); } } @Override public void verify(final byte[] code) { context.verify(code); } @Override public long getUniqueScriptId() { return context.getUniqueScriptId(); } @Override public void storeScript(final String cacheKey, final Source source, final String mainClassName, final Map<String,byte[]> classBytes, final Map<Integer, FunctionInitializer> initializers, final Object[] constants, final int compilationId) { if (context.codeStore != null) { context.codeStore.store(cacheKey, source, mainClassName, classBytes, initializers, constants, compilationId); } } @Override public StoredScript loadScript(final Source source, final String functionKey) { if (context.codeStore != null) { return context.codeStore.load(source, functionKey); } return null; } @Override public boolean isCompatibleWith(final CodeInstaller other) { if (other instanceof ContextCodeInstaller) { final ContextCodeInstaller cci = (ContextCodeInstaller)other; return cci.context == context && cci.codeSource == codeSource; } return false; } } private static class NamedContextCodeInstaller extends ContextCodeInstaller { private final ScriptLoader loader; private int usageCount = 0; private int bytesDefined = 0; // We reuse this installer for 10 compilations or 200000 defined bytes. Usually the first condition // will occur much earlier, the second is a safety measure for very large scripts/functions. private final static int MAX_USAGES = 10; private final static int MAX_BYTES_DEFINED = 200_000; private NamedContextCodeInstaller(final Context context, final CodeSource codeSource, final ScriptLoader loader) { super(context, codeSource); this.loader = loader; } @Override public Class<?> install(final String className, final byte[] bytecode) { usageCount++; bytesDefined += bytecode.length; NAMED_INSTALLED_SCRIPT_COUNT.increment(); return loader.installClass(Compiler.binaryName(className), bytecode, codeSource); } @Override public CodeInstaller getOnDemandCompilationInstaller() { // Reuse this installer if we're within our limits. if (usageCount < MAX_USAGES && bytesDefined < MAX_BYTES_DEFINED) { return this; } return new NamedContextCodeInstaller(context, codeSource, context.createNewLoader()); } @Override public CodeInstaller getMultiClassCodeInstaller() { // This installer is perfectly suitable for installing multiple classes that reference each other // as it produces classes with resolvable names, all defined in a single class loader. return this; } } private final WeakValueCache<CodeSource, Class<?>> anonymousHostClasses = new WeakValueCache<>(); private static final class AnonymousContextCodeInstaller extends ContextCodeInstaller { private static final Unsafe UNSAFE = Unsafe.getUnsafe(); private static final String ANONYMOUS_HOST_CLASS_NAME = Compiler.SCRIPTS_PACKAGE.replace('/', '.') + ".AnonymousHost"; private static final byte[] ANONYMOUS_HOST_CLASS_BYTES = getAnonymousHostClassBytes(); private final Class<?> hostClass; private AnonymousContextCodeInstaller(final Context context, final CodeSource codeSource, final Class<?> hostClass) { super(context, codeSource); this.hostClass = hostClass; } @Override public Class<?> install(final String className, final byte[] bytecode) { ANONYMOUS_INSTALLED_SCRIPT_COUNT.increment(); return UNSAFE.defineAnonymousClass(hostClass, bytecode, null); } @Override public CodeInstaller getOnDemandCompilationInstaller() { // This code loader can be indefinitely reused for on-demand recompilations for the same code source. return this; } @Override public CodeInstaller getMultiClassCodeInstaller() { // This code loader can not be used to install multiple classes that reference each other, as they // would have no resolvable names. Therefore, in such situation we must revert to an installer that // produces named classes. return new NamedContextCodeInstaller(context, codeSource, context.createNewLoader()); } private static byte[] getAnonymousHostClassBytes() { final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); cw.visit(V1_7, Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT, ANONYMOUS_HOST_CLASS_NAME.replace('.', '/'), null, "java/lang/Object", null); cw.visitEnd(); return cw.toByteArray(); } }
Is Context global debug mode enabled ?
/** Is Context global debug mode enabled ? */
public static final boolean DEBUG = Options.getBooleanProperty("nashorn.debug"); private static final ThreadLocal<Global> currentGlobal = new ThreadLocal<>(); // in-memory cache for loaded classes private ClassCache classCache; // persistent code store private CodeStore codeStore; // A factory for linking global properties as constant method handles. It is created when the first Global // is created, and invalidated forever once the second global is created. private final AtomicReference<GlobalConstants> globalConstantsRef = new AtomicReference<>(); // Are java.sql, java.sql.rowset modules found in the system? static final boolean javaSqlFound, javaSqlRowsetFound; static { final ModuleLayer boot = ModuleLayer.boot(); javaSqlFound = boot.findModule("java.sql").isPresent(); javaSqlRowsetFound = boot.findModule("java.sql.rowset").isPresent(); }
Get the current global scope
Returns:the current global scope
/** * Get the current global scope * @return the current global scope */
public static Global getGlobal() { // This class in a package.access protected package. // Trusted code only can call this method. return currentGlobal.get(); }
Set the current global scope
Params:
  • global – the global scope
/** * Set the current global scope * @param global the global scope */
public static void setGlobal(final ScriptObject global) { if (global != null && !(global instanceof Global)) { throw new IllegalArgumentException("not a global!"); } setGlobal((Global)global); }
Set the current global scope
Params:
  • global – the global scope
/** * Set the current global scope * @param global the global scope */
public static void setGlobal(final Global global) { // This class in a package.access protected package. // Trusted code only can call this method. assert getGlobal() != global; //same code can be cached between globals, then we need to invalidate method handle constants if (global != null) { final GlobalConstants globalConstants = getContext(global).getGlobalConstants(); if (globalConstants != null) { globalConstants.invalidateAll(); } } currentGlobal.set(global); }
Get context of the current global
Returns:current global scope's context.
/** * Get context of the current global * @return current global scope's context. */
public static Context getContext() { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission(NASHORN_GET_CONTEXT)); } return getContextTrusted(); }
Get current context's error writer
Returns:error writer of the current context
/** * Get current context's error writer * * @return error writer of the current context */
public static PrintWriter getCurrentErr() { final ScriptObject global = getGlobal(); return (global != null)? global.getContext().getErr() : new PrintWriter(System.err); }
Output text to this Context's error stream
Params:
  • str – text to write
/** * Output text to this Context's error stream * @param str text to write */
public static void err(final String str) { err(str, true); }
Output text to this Context's error stream, optionally with a newline afterwards
Params:
  • str – text to write
  • crlf – write a carriage return/new line after text
/** * Output text to this Context's error stream, optionally with * a newline afterwards * * @param str text to write * @param crlf write a carriage return/new line after text */
public static void err(final String str, final boolean crlf) { final PrintWriter err = Context.getCurrentErr(); if (err != null) { if (crlf) { err.println(str); } else { err.print(str); } } }
Current environment.
/** Current environment. */
private final ScriptEnvironment env;
is this context in strict mode? Cached from env. as this is used heavily.
/** is this context in strict mode? Cached from env. as this is used heavily. */
final boolean _strict;
class loader to resolve classes from script.
/** class loader to resolve classes from script. */
private final ClassLoader appLoader; /*package-private*/ ClassLoader getAppLoader() { return appLoader; }
Class loader to load classes compiled from scripts.
/** Class loader to load classes compiled from scripts. */
private final ScriptLoader scriptLoader;
Dynamic linker for linking call sites in script code loaded by this context
/** Dynamic linker for linking call sites in script code loaded by this context */
private final DynamicLinker dynamicLinker;
Current error manager.
/** Current error manager. */
private final ErrorManager errors;
Unique id for script. Used only when --loader-per-compile=false
/** Unique id for script. Used only when --loader-per-compile=false */
private final AtomicLong uniqueScriptId;
Optional class filter to use for Java classes. Can be null.
/** Optional class filter to use for Java classes. Can be null. */
private final ClassFilter classFilter;
Process-wide singleton structure loader
/** Process-wide singleton structure loader */
private static final StructureLoader theStructLoader; private static final ConcurrentMap<String, Class<?>> structureClasses = new ConcurrentHashMap<>(); /*package-private*/ @SuppressWarnings("static-method") StructureLoader getStructLoader() { return theStructLoader; } private static AccessControlContext createNoPermAccCtxt() { return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, new Permissions()) }); } private static AccessControlContext createPermAccCtxt(final String permName) { final Permissions perms = new Permissions(); perms.add(new RuntimePermission(permName)); return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) }); } private static final AccessControlContext NO_PERMISSIONS_ACC_CTXT = createNoPermAccCtxt(); private static final AccessControlContext CREATE_LOADER_ACC_CTXT = createPermAccCtxt("createClassLoader"); private static final AccessControlContext CREATE_GLOBAL_ACC_CTXT = createPermAccCtxt(NASHORN_CREATE_GLOBAL); private static final AccessControlContext GET_LOADER_ACC_CTXT = createPermAccCtxt("getClassLoader"); static { final ClassLoader myLoader = Context.class.getClassLoader(); theStructLoader = AccessController.doPrivileged(new PrivilegedAction<StructureLoader>() { @Override public StructureLoader run() { return new StructureLoader(myLoader); } }, CREATE_LOADER_ACC_CTXT); }
ThrowErrorManager that throws ParserException upon error conditions.
/** * ThrowErrorManager that throws ParserException upon error conditions. */
public static class ThrowErrorManager extends ErrorManager { @Override public void error(final String message) { throw new ParserException(message); } @Override public void error(final ParserException e) { throw e; } }
Constructor
Params:
  • options – options from command line or Context creator
  • errors – error manger
  • appLoader – application class loader
/** * Constructor * * @param options options from command line or Context creator * @param errors error manger * @param appLoader application class loader */
public Context(final Options options, final ErrorManager errors, final ClassLoader appLoader) { this(options, errors, appLoader, null); }
Constructor
Params:
  • options – options from command line or Context creator
  • errors – error manger
  • appLoader – application class loader
  • classFilter – class filter to use
/** * Constructor * * @param options options from command line or Context creator * @param errors error manger * @param appLoader application class loader * @param classFilter class filter to use */
public Context(final Options options, final ErrorManager errors, final ClassLoader appLoader, final ClassFilter classFilter) { this(options, errors, new PrintWriter(System.out, true), new PrintWriter(System.err, true), appLoader, classFilter); }
Constructor
Params:
  • options – options from command line or Context creator
  • errors – error manger
  • out – output writer for this Context
  • err – error writer for this Context
  • appLoader – application class loader
/** * Constructor * * @param options options from command line or Context creator * @param errors error manger * @param out output writer for this Context * @param err error writer for this Context * @param appLoader application class loader */
public Context(final Options options, final ErrorManager errors, final PrintWriter out, final PrintWriter err, final ClassLoader appLoader) { this(options, errors, out, err, appLoader, (ClassFilter)null); }
Constructor
Params:
  • options – options from command line or Context creator
  • errors – error manger
  • out – output writer for this Context
  • err – error writer for this Context
  • appLoader – application class loader
  • classFilter – class filter to use
/** * Constructor * * @param options options from command line or Context creator * @param errors error manger * @param out output writer for this Context * @param err error writer for this Context * @param appLoader application class loader * @param classFilter class filter to use */
public Context(final Options options, final ErrorManager errors, final PrintWriter out, final PrintWriter err, final ClassLoader appLoader, final ClassFilter classFilter) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission(NASHORN_CREATE_CONTEXT)); } this.classFilter = classFilter; this.env = new ScriptEnvironment(options, out, err); this._strict = env._strict; if (env._loader_per_compile) { this.scriptLoader = null; this.uniqueScriptId = null; } else { this.scriptLoader = createNewLoader(); this.uniqueScriptId = new AtomicLong(); } this.errors = errors; // if user passed --module-path, we create a module class loader with // passed appLoader as the parent. final String modulePath = env._module_path; ClassLoader appCl = null; if (!env._compile_only && modulePath != null && !modulePath.isEmpty()) { // make sure that caller can create a class loader. if (sm != null) { sm.checkCreateClassLoader(); } appCl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { @Override public ClassLoader run() { return createModuleLoader(appLoader, modulePath, env._add_modules); } }); } else { appCl = appLoader; } // if user passed -classpath option, make a URLClassLoader with that and // the app loader or module app loader as the parent. final String classPath = env._classpath; if (!env._compile_only && classPath != null && !classPath.isEmpty()) { // make sure that caller can create a class loader. if (sm != null) { sm.checkCreateClassLoader(); } appCl = NashornLoader.createClassLoader(classPath, appCl); } this.appLoader = appCl; this.dynamicLinker = Bootstrap.createDynamicLinker(this.appLoader, env._unstable_relink_threshold); final int cacheSize = env._class_cache_size; if (cacheSize > 0) { classCache = new ClassCache(this, cacheSize); } if (env._persistent_cache) { codeStore = newCodeStore(this); } // print version info if asked. if (env._version) { getErr().println("nashorn " + Version.version()); } if (env._fullversion) { getErr().println("nashorn full version " + Version.fullVersion()); } if (Options.getBooleanProperty("nashorn.fields.dual")) { fieldMode = FieldMode.DUAL; } else if (Options.getBooleanProperty("nashorn.fields.objects")) { fieldMode = FieldMode.OBJECTS; } else { fieldMode = FieldMode.AUTO; } initLoggers(); }
Get the class filter for this context
Returns:class filter
/** * Get the class filter for this context * @return class filter */
public ClassFilter getClassFilter() { return classFilter; }
Returns the factory for constant method handles for global properties. The returned factory can be invalidated if this Context has more than one Global.
Returns:the factory for constant method handles for global properties.
/** * Returns the factory for constant method handles for global properties. The returned factory can be * invalidated if this Context has more than one Global. * @return the factory for constant method handles for global properties. */
GlobalConstants getGlobalConstants() { return globalConstantsRef.get(); }
Get the error manager for this context
Returns:error manger
/** * Get the error manager for this context * @return error manger */
public ErrorManager getErrorManager() { return errors; }
Get the script environment for this context
Returns:script environment
/** * Get the script environment for this context * @return script environment */
public ScriptEnvironment getEnv() { return env; }
Get the output stream for this context
Returns:output print writer
/** * Get the output stream for this context * @return output print writer */
public PrintWriter getOut() { return env.getOut(); }
Get the error stream for this context
Returns:error print writer
/** * Get the error stream for this context * @return error print writer */
public PrintWriter getErr() { return env.getErr(); }
Should scripts compiled by this context use dual field representation?
Returns:true if using dual fields, false for object-only fields
/** * Should scripts compiled by this context use dual field representation? * @return true if using dual fields, false for object-only fields */
public boolean useDualFields() { return fieldMode == FieldMode.DUAL || (fieldMode == FieldMode.AUTO && env._optimistic_types); }
Get the PropertyMap of the current global scope
Returns:the property map of the current global scope
/** * Get the PropertyMap of the current global scope * @return the property map of the current global scope */
public static PropertyMap getGlobalMap() { return Context.getGlobal().getMap(); }
Compile a top level script.
Params:
  • source – the source
  • scope – the scope
Returns:top level function for script
/** * Compile a top level script. * * @param source the source * @param scope the scope * * @return top level function for script */
public ScriptFunction compileScript(final Source source, final ScriptObject scope) { return compileScript(source, scope, this.errors); }
Interface to represent compiled code that can be re-used across many global scope instances
/** * Interface to represent compiled code that can be re-used across many * global scope instances */
public static interface MultiGlobalCompiledScript {
Obtain script function object for a specific global scope object.
Params:
  • newGlobal – global scope for which function object is obtained
Returns:script function for script level expressions
/** * Obtain script function object for a specific global scope object. * * @param newGlobal global scope for which function object is obtained * @return script function for script level expressions */
public ScriptFunction getFunction(final Global newGlobal); }
Compile a top level script.
Params:
  • source – the script source
Returns:reusable compiled script across many global scopes.
/** * Compile a top level script. * * @param source the script source * @return reusable compiled script across many global scopes. */
public MultiGlobalCompiledScript compileScript(final Source source) { final Class<?> clazz = compile(source, this.errors, this._strict, false); final MethodHandle createProgramFunctionHandle = getCreateProgramFunctionHandle(clazz); return new MultiGlobalCompiledScript() { @Override public ScriptFunction getFunction(final Global newGlobal) { return invokeCreateProgramFunctionHandle(createProgramFunctionHandle, newGlobal); } }; }
Entry point for eval
Params:
  • initialScope – The scope of this eval call
  • string – Evaluated code as a String
  • callThis – "this" to be passed to the evaluated code
  • location – location of the eval call
Returns:the return value of the eval
/** * Entry point for {@code eval} * * @param initialScope The scope of this eval call * @param string Evaluated code as a String * @param callThis "this" to be passed to the evaluated code * @param location location of the eval call * @return the return value of the {@code eval} */
public Object eval(final ScriptObject initialScope, final String string, final Object callThis, final Object location) { return eval(initialScope, string, callThis, location, false, false); }
Entry point for eval
Params:
  • initialScope – The scope of this eval call
  • string – Evaluated code as a String
  • callThis – "this" to be passed to the evaluated code
  • location – location of the eval call
  • strict – is this eval call from a strict mode code?
  • evalCall – is this called from "eval" builtin?
Returns:the return value of the eval
/** * Entry point for {@code eval} * * @param initialScope The scope of this eval call * @param string Evaluated code as a String * @param callThis "this" to be passed to the evaluated code * @param location location of the eval call * @param strict is this {@code eval} call from a strict mode code? * @param evalCall is this called from "eval" builtin? * * @return the return value of the {@code eval} */
public Object eval(final ScriptObject initialScope, final String string, final Object callThis, final Object location, final boolean strict, final boolean evalCall) { final String file = location == UNDEFINED || location == null ? "<eval>" : location.toString(); final Source source = sourceFor(file, string, evalCall); // is this direct 'eval' builtin call? final boolean directEval = evalCall && (location != UNDEFINED); final Global global = Context.getGlobal(); ScriptObject scope = initialScope; // ECMA section 10.1.1 point 2 says eval code is strict if it begins // with "use strict" directive or eval direct call itself is made // from from strict mode code. We are passed with caller's strict mode. // Nashorn extension: any 'eval' is unconditionally strict when -strict is specified. boolean strictFlag = strict || this._strict; Class<?> clazz; try { clazz = compile(source, new ThrowErrorManager(), strictFlag, true); } catch (final ParserException e) { e.throwAsEcmaException(global); return null; } if (!strictFlag) { // We need to get strict mode flag from compiled class. This is // because eval code may start with "use strict" directive. try { strictFlag = clazz.getField(STRICT_MODE.symbolName()).getBoolean(null); } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { //ignored strictFlag = false; } } // In strict mode, eval does not instantiate variables and functions // in the caller's environment. A new environment is created! if (strictFlag) { // Create a new scope object with given scope as its prototype scope = newScope(scope); } final ScriptFunction func = getProgramFunction(clazz, scope); Object evalThis; if (directEval) { evalThis = (callThis != UNDEFINED && callThis != null) || strictFlag ? callThis : global; } else { // either indirect evalCall or non-eval (Function, engine.eval, ScriptObjectMirror.eval..) evalThis = callThis; } return ScriptRuntime.apply(func, evalThis); } private static ScriptObject newScope(final ScriptObject callerScope) { return new Scope(callerScope, PropertyMap.newMap(Scope.class)); } private static Source loadInternal(final String srcStr, final String prefix, final String resourcePath) { if (srcStr.startsWith(prefix)) { final String resource = resourcePath + srcStr.substring(prefix.length()); // NOTE: even sandbox scripts should be able to load scripts in nashorn: scheme // These scripts are always available and are loaded from nashorn.jar's resources. return AccessController.doPrivileged( new PrivilegedAction<Source>() { @Override public Source run() { try { final InputStream resStream = Context.class.getResourceAsStream(resource); return resStream != null ? sourceFor(srcStr, Source.readFully(resStream)) : null; } catch (final IOException exp) { return null; } } }); } return null; }
Implementation of load Nashorn extension. Load a script file from a source expression
Params:
  • scope – the scope
  • from – source expression for script
Throws:
Returns:return value for load call (undefined)
/** * Implementation of {@code load} Nashorn extension. Load a script file from a source * expression * * @param scope the scope * @param from source expression for script * * @return return value for load call (undefined) * * @throws IOException if source cannot be found or loaded */
public Object load(final Object scope, final Object from) throws IOException { final Object src = from instanceof ConsString ? from.toString() : from; Source source = null; // load accepts a String (which could be a URL or a file name), a File, a URL // or a ScriptObject that has "name" and "source" (string valued) properties. if (src instanceof String) { final String srcStr = (String)src; if (srcStr.startsWith(LOAD_CLASSPATH)) { final URL url = getResourceURL(srcStr.substring(LOAD_CLASSPATH.length())); source = url != null ? sourceFor(url.toString(), url) : null; } else { final File file = new File(srcStr); if (srcStr.indexOf(':') != -1) { if ((source = loadInternal(srcStr, LOAD_NASHORN, "resources/")) == null && (source = loadInternal(srcStr, LOAD_FX, "resources/fx/")) == null) { URL url; try { //check for malformed url. if malformed, it may still be a valid file url = new URL(srcStr); } catch (final MalformedURLException e) { url = file.toURI().toURL(); } source = sourceFor(url.toString(), url); } } else if (file.isFile()) { source = sourceFor(srcStr, file); } } } else if (src instanceof File && ((File)src).isFile()) { final File file = (File)src; source = sourceFor(file.getName(), file); } else if (src instanceof URL) { final URL url = (URL)src; source = sourceFor(url.toString(), url); } else if (src instanceof ScriptObject) { final ScriptObject sobj = (ScriptObject)src; if (sobj.has("script") && sobj.has("name")) { final String script = JSType.toString(sobj.get("script")); final String name = JSType.toString(sobj.get("name")); source = sourceFor(name, script); } } else if (src instanceof Map) { final Map<?,?> map = (Map<?,?>)src; if (map.containsKey("script") && map.containsKey("name")) { final String script = JSType.toString(map.get("script")); final String name = JSType.toString(map.get("name")); source = sourceFor(name, script); } } if (source != null) { if (scope instanceof ScriptObject && ((ScriptObject)scope).isScope()) { final ScriptObject sobj = (ScriptObject)scope; // passed object is a script object // Global is the only user accessible scope ScriptObject assert sobj.isGlobal() : "non-Global scope object!!"; return evaluateSource(source, sobj, sobj); } else if (scope == null || scope == UNDEFINED) { // undefined or null scope. Use current global instance. final Global global = getGlobal(); return evaluateSource(source, global, global); } else { /* * Arbitrary object passed for scope. * Indirect load that is equivalent to: * * (function(scope, source) { * with (scope) { * eval(<script_from_source>); * } * })(scope, source); */ final Global global = getGlobal(); // Create a new object. This is where all declarations // (var, function) from the evaluated code go. // make global to be its __proto__ so that global // definitions are accessible to the evaluated code. final ScriptObject evalScope = newScope(global); // finally, make a WithObject around user supplied scope object // so that it's properties are accessible as variables. final ScriptObject withObj = ScriptRuntime.openWith(evalScope, scope); // evaluate given source with 'withObj' as scope // but use global object as "this". return evaluateSource(source, withObj, global); } } throw typeError("cant.load.script", ScriptRuntime.safeToString(from)); }
Implementation of loadWithNewGlobal Nashorn extension. Load a script file from a source expression, after creating a new global scope.
Params:
  • from – source expression for script
  • args – (optional) arguments to be passed to the loaded script
Throws:
Returns:return value for load call (undefined)
/** * Implementation of {@code loadWithNewGlobal} Nashorn extension. Load a script file from a source * expression, after creating a new global scope. * * @param from source expression for script * @param args (optional) arguments to be passed to the loaded script * * @return return value for load call (undefined) * * @throws IOException if source cannot be found or loaded */
public Object loadWithNewGlobal(final Object from, final Object...args) throws IOException { final Global oldGlobal = getGlobal(); final Global newGlobal = AccessController.doPrivileged(new PrivilegedAction<Global>() { @Override public Global run() { try { return newGlobal(); } catch (final RuntimeException e) { if (Context.DEBUG) { e.printStackTrace(); } throw e; } } }, CREATE_GLOBAL_ACC_CTXT); // initialize newly created Global instance initGlobal(newGlobal); setGlobal(newGlobal); final Object[] wrapped = args == null? ScriptRuntime.EMPTY_ARRAY : ScriptObjectMirror.wrapArray(args, oldGlobal); newGlobal.put("arguments", newGlobal.wrapAsObject(wrapped), env._strict); try { // wrap objects from newGlobal's world as mirrors - but if result // is from oldGlobal's world, unwrap it! return ScriptObjectMirror.unwrap(ScriptObjectMirror.wrap(load(newGlobal, from), newGlobal), oldGlobal); } finally { setGlobal(oldGlobal); } }
Load or get a structure class. Structure class names are based on the number of parameter fields and AccessorProperty fields in them. Structure classes are used to represent ScriptObjects
Params:
  • fullName – full name of class, e.g. jdk.nashorn.internal.objects.JO2P1 contains 2 fields and 1 parameter.
Throws:
See Also:
Returns:the Class<?> for this structure
/** * Load or get a structure class. Structure class names are based on the number of parameter fields * and {@link AccessorProperty} fields in them. Structure classes are used to represent ScriptObjects * * @see ObjectClassGenerator * @see AccessorProperty * @see ScriptObject * * @param fullName full name of class, e.g. jdk.nashorn.internal.objects.JO2P1 contains 2 fields and 1 parameter. * * @return the {@code Class<?>} for this structure * * @throws ClassNotFoundException if structure class cannot be resolved */
@SuppressWarnings("unchecked") public static Class<? extends ScriptObject> forStructureClass(final String fullName) throws ClassNotFoundException { if (System.getSecurityManager() != null && !StructureLoader.isStructureClass(fullName)) { throw new ClassNotFoundException(fullName); } return (Class<? extends ScriptObject>)structureClasses.computeIfAbsent(fullName, (name) -> { try { return Class.forName(name, true, theStructLoader); } catch (final ClassNotFoundException e) { throw new AssertionError(e); } }); }
Is className the name of a structure class?
Params:
  • className – a class name
Returns:true if className is a structure class name
/** * Is {@code className} the name of a structure class? * * @param className a class name * @return true if className is a structure class name */
public static boolean isStructureClass(final String className) { return StructureLoader.isStructureClass(className); }
Checks that the given Class can be accessed from no permissions context.
Params:
  • clazz – Class object
Throws:
/** * Checks that the given Class can be accessed from no permissions context. * * @param clazz Class object * @throws SecurityException if not accessible */
public static void checkPackageAccess(final Class<?> clazz) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { Class<?> bottomClazz = clazz; while (bottomClazz.isArray()) { bottomClazz = bottomClazz.getComponentType(); } checkPackageAccess(sm, bottomClazz.getName()); } }
Checks that the given package name can be accessed from no permissions context.
Params:
  • pkgName – package name
Throws:
/** * Checks that the given package name can be accessed from no permissions context. * * @param pkgName package name * @throws SecurityException if not accessible */
public static void checkPackageAccess(final String pkgName) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkPackageAccess(sm, pkgName.endsWith(".") ? pkgName : pkgName + "."); } }
Checks that the given package can be accessed from no permissions context.
Params:
  • sm – current security manager instance
  • fullName – fully qualified package name
@throwSecurityException if not accessible
/** * Checks that the given package can be accessed from no permissions context. * * @param sm current security manager instance * @param fullName fully qualified package name * @throw SecurityException if not accessible */
private static void checkPackageAccess(final SecurityManager sm, final String fullName) { Objects.requireNonNull(sm); final int index = fullName.lastIndexOf('.'); if (index != -1) { final String pkgName = fullName.substring(0, index); AccessController.doPrivileged(new PrivilegedAction<Void>() { @Override public Void run() { sm.checkPackageAccess(pkgName); return null; } }, NO_PERMISSIONS_ACC_CTXT); } }
Checks that the given Class can be accessed from no permissions context.
Params:
  • clazz – Class object
Returns:true if package is accessible, false otherwise
/** * Checks that the given Class can be accessed from no permissions context. * * @param clazz Class object * @return true if package is accessible, false otherwise */
private static boolean isAccessiblePackage(final Class<?> clazz) { try { checkPackageAccess(clazz); return true; } catch (final SecurityException se) { return false; } }
Checks that the given Class is public and it can be accessed from no permissions context.
Params:
  • clazz – Class object to check
Returns:true if Class is accessible, false otherwise
/** * Checks that the given Class is public and it can be accessed from no permissions context. * * @param clazz Class object to check * @return true if Class is accessible, false otherwise */
public static boolean isAccessibleClass(final Class<?> clazz) { return Modifier.isPublic(clazz.getModifiers()) && Context.isAccessiblePackage(clazz); }
Lookup a Java class. This is used for JSR-223 stuff linking in from jdk.nashorn.internal.objects.NativeJava and jdk.nashorn.internal.runtime.NativeJavaPackage
Params:
  • fullName – full name of class to load
Throws:
Returns:the Class<?> for the name
/** * Lookup a Java class. This is used for JSR-223 stuff linking in from * {@code jdk.nashorn.internal.objects.NativeJava} and {@code jdk.nashorn.internal.runtime.NativeJavaPackage} * * @param fullName full name of class to load * * @return the {@code Class<?>} for the name * * @throws ClassNotFoundException if class cannot be resolved */
public Class<?> findClass(final String fullName) throws ClassNotFoundException { if (fullName.indexOf('[') != -1 || fullName.indexOf('/') != -1) { // don't allow array class names or internal names. throw new ClassNotFoundException(fullName); } // give chance to ClassFilter to filter out, if present if (classFilter != null && !classFilter.exposeToScripts(fullName)) { throw new ClassNotFoundException(fullName); } // check package access as soon as possible! final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkPackageAccess(sm, fullName); } // Try finding using the "app" loader. if (appLoader != null) { return Class.forName(fullName, true, appLoader); } else { final Class<?> cl = Class.forName(fullName); // return the Class only if it was loaded by boot loader if (cl.getClassLoader() == null) { return cl; } else { throw new ClassNotFoundException(fullName); } } }
Hook to print stack trace for a Throwable that occurred during execution
Params:
  • t – throwable for which to dump stack
/** * Hook to print stack trace for a {@link Throwable} that occurred during * execution * * @param t throwable for which to dump stack */
public static void printStackTrace(final Throwable t) { if (Context.DEBUG) { t.printStackTrace(Context.getCurrentErr()); } }
Verify generated bytecode before emission. This is called back from the ObjectClassGenerator or the Compiler. If the "--verify-code" parameter hasn't been given, this is a nop Note that verification may load classes -- we don't want to do that unless user specified verify option. We check it here even though caller may have already checked that flag
Params:
  • bytecode – bytecode to verify
/** * Verify generated bytecode before emission. This is called back from the * {@link ObjectClassGenerator} or the {@link Compiler}. If the "--verify-code" parameter * hasn't been given, this is a nop * * Note that verification may load classes -- we don't want to do that unless * user specified verify option. We check it here even though caller * may have already checked that flag * * @param bytecode bytecode to verify */
public void verify(final byte[] bytecode) { if (env._verify_code) { // No verification when security manager is around as verifier // may load further classes - which should be avoided. if (System.getSecurityManager() == null) { CheckClassAdapter.verify(new ClassReader(bytecode), theStructLoader, false, new PrintWriter(System.err, true)); } } }
Create and initialize a new global scope object.
Returns:the initialized global scope object.
/** * Create and initialize a new global scope object. * * @return the initialized global scope object. */
public Global createGlobal() { return initGlobal(newGlobal()); }
Create a new uninitialized global scope object
Returns:the global script object
/** * Create a new uninitialized global scope object * @return the global script object */
public Global newGlobal() { createOrInvalidateGlobalConstants(); return new Global(this); } private void createOrInvalidateGlobalConstants() { for (;;) { final GlobalConstants currentGlobalConstants = getGlobalConstants(); if (currentGlobalConstants != null) { // Subsequent invocation; we're creating our second or later Global. GlobalConstants is not safe to use // with more than one Global, as the constant method handle linkages it creates create a coupling // between the Global and the call sites in the compiled code. currentGlobalConstants.invalidateForever(); return; } final GlobalConstants newGlobalConstants = new GlobalConstants(getLogger(GlobalConstants.class)); if (globalConstantsRef.compareAndSet(null, newGlobalConstants)) { // First invocation; we're creating the first Global in this Context. Create the GlobalConstants object // for this Context. return; } // If we reach here, then we started out as the first invocation, but another concurrent invocation won the // CAS race. We'll just let the loop repeat and invalidate the CAS race winner. } }
Initialize given global scope object.
Params:
  • global – the global
  • engine – the associated ScriptEngine instance, can be null
Returns:the initialized global scope object.
/** * Initialize given global scope object. * * @param global the global * @param engine the associated ScriptEngine instance, can be null * @return the initialized global scope object. */
public Global initGlobal(final Global global, final ScriptEngine engine) { // Need only minimal global object, if we are just compiling. if (!env._compile_only) { final Global oldGlobal = Context.getGlobal(); try { Context.setGlobal(global); // initialize global scope with builtin global objects global.initBuiltinObjects(engine); } finally { Context.setGlobal(oldGlobal); } } return global; }
Initialize given global scope object.
Params:
  • global – the global
Returns:the initialized global scope object.
/** * Initialize given global scope object. * * @param global the global * @return the initialized global scope object. */
public Global initGlobal(final Global global) { return initGlobal(global, null); }
Return the current global's context
Returns:current global's context
/** * Return the current global's context * @return current global's context */
static Context getContextTrusted() { return getContext(getGlobal()); }
Gets the Nashorn dynamic linker for the specified class. If the class is a script class, the dynamic linker associated with its context is returned. Otherwise the dynamic linker associated with the current context is returned.
Params:
  • clazz – the class for which we want to retrieve a dynamic linker.
Returns:the Nashorn dynamic linker for the specified class.
/** * Gets the Nashorn dynamic linker for the specified class. If the class is * a script class, the dynamic linker associated with its context is * returned. Otherwise the dynamic linker associated with the current * context is returned. * @param clazz the class for which we want to retrieve a dynamic linker. * @return the Nashorn dynamic linker for the specified class. */
public static DynamicLinker getDynamicLinker(final Class<?> clazz) { return fromClass(clazz).dynamicLinker; }
Gets the Nashorn dynamic linker associated with the current context.
Returns:the Nashorn dynamic linker for the current context.
/** * Gets the Nashorn dynamic linker associated with the current context. * @return the Nashorn dynamic linker for the current context. */
public static DynamicLinker getDynamicLinker() { return getContextTrusted().dynamicLinker; }
Creates a module layer with one module that is defined to the given class loader.
Params:
  • descriptor – the module descriptor for the newly created module
  • loader – the class loader of the module
Returns:the new Module
/** * Creates a module layer with one module that is defined to the given class * loader. * * @param descriptor the module descriptor for the newly created module * @param loader the class loader of the module * @return the new Module */
static Module createModuleTrusted(final ModuleDescriptor descriptor, final ClassLoader loader) { return createModuleTrusted(ModuleLayer.boot(), descriptor, loader); }
Creates a module layer with one module that is defined to the given class loader.
Params:
  • parent – the parent layer of the new module
  • descriptor – the module descriptor for the newly created module
  • loader – the class loader of the module
Returns:the new Module
/** * Creates a module layer with one module that is defined to the given class * loader. * * @param parent the parent layer of the new module * @param descriptor the module descriptor for the newly created module * @param loader the class loader of the module * @return the new Module */
static Module createModuleTrusted(final ModuleLayer parent, final ModuleDescriptor descriptor, final ClassLoader loader) { final String mn = descriptor.name(); final ModuleReference mref = new ModuleReference(descriptor, null) { @Override public ModuleReader open() { throw new UnsupportedOperationException(); } }; final ModuleFinder finder = new ModuleFinder() { @Override public Optional<ModuleReference> find(final String name) { if (name.equals(mn)) { return Optional.of(mref); } else { return Optional.empty(); } } @Override public Set<ModuleReference> findAll() { return Set.of(mref); } }; final Configuration cf = parent.configuration() .resolve(finder, ModuleFinder.of(), Set.of(mn)); final PrivilegedAction<ModuleLayer> pa = () -> parent.defineModules(cf, name -> loader); final ModuleLayer layer = AccessController.doPrivileged(pa, GET_LOADER_ACC_CTXT); final Module m = layer.findModule(mn).get(); assert m.getLayer() == layer; return m; } static Context getContextTrustedOrNull() { final Global global = Context.getGlobal(); return global == null ? null : getContext(global); } private static Context getContext(final Global global) { // We can't invoke Global.getContext() directly, as it's a protected override, and Global isn't in our package. // In order to access the method, we must cast it to ScriptObject first (which is in our package) and then let // virtual invocation do its thing. return ((ScriptObject)global).getContext(); }
Try to infer Context instance from the Class. If we cannot, then get it from the thread local variable.
Params:
  • clazz – the class
Returns:context
/** * Try to infer Context instance from the Class. If we cannot, * then get it from the thread local variable. * * @param clazz the class * @return context */
static Context fromClass(final Class<?> clazz) { ClassLoader loader = null; try { loader = clazz.getClassLoader(); } catch (final SecurityException ignored) { // This could fail because of anonymous classes being used. // Accessing loader of anonymous class fails (for extension // loader class too?). In any case, for us fetching Context // from class loader is just an optimization. We can always // get Context from thread local storage (below). } if (loader instanceof ScriptLoader) { return ((ScriptLoader)loader).getContext(); } return Context.getContextTrusted(); } private URL getResourceURL(final String resName) { if (appLoader != null) { return appLoader.getResource(resName); } return ClassLoader.getSystemResource(resName); } private Object evaluateSource(final Source source, final ScriptObject scope, final ScriptObject thiz) { ScriptFunction script = null; try { script = compileScript(source, scope, new Context.ThrowErrorManager()); } catch (final ParserException e) { e.throwAsEcmaException(); } return ScriptRuntime.apply(script, thiz); } private static ScriptFunction getProgramFunction(final Class<?> script, final ScriptObject scope) { if (script == null) { return null; } return invokeCreateProgramFunctionHandle(getCreateProgramFunctionHandle(script), scope); } private static MethodHandle getCreateProgramFunctionHandle(final Class<?> script) { try { return LOOKUP.findStatic(script, CREATE_PROGRAM_FUNCTION.symbolName(), CREATE_PROGRAM_FUNCTION_TYPE); } catch (NoSuchMethodException | IllegalAccessException e) { throw new AssertionError("Failed to retrieve a handle for the program function for " + script.getName(), e); } } private static ScriptFunction invokeCreateProgramFunctionHandle(final MethodHandle createProgramFunctionHandle, final ScriptObject scope) { try { return (ScriptFunction)createProgramFunctionHandle.invokeExact(scope); } catch (final RuntimeException|Error e) { throw e; } catch (final Throwable t) { throw new AssertionError("Failed to create a program function", t); } } private ScriptFunction compileScript(final Source source, final ScriptObject scope, final ErrorManager errMan) { return getProgramFunction(compile(source, errMan, this._strict, false), scope); } private synchronized Class<?> compile(final Source source, final ErrorManager errMan, final boolean strict, final boolean isEval) { // start with no errors, no warnings. errMan.reset(); Class<?> script = findCachedClass(source); if (script != null) { final DebugLogger log = getLogger(Compiler.class); if (log.isEnabled()) { log.fine(new RuntimeEvent<>(Level.INFO, source), "Code cache hit for ", source, " avoiding recompile."); } return script; } StoredScript storedScript = null; FunctionNode functionNode = null; // Don't use code store if optimistic types is enabled but lazy compilation is not. // This would store a full script compilation with many wrong optimistic assumptions that would // do more harm than good on later runs with both optimistic types and lazy compilation enabled. final boolean useCodeStore = codeStore != null && !env._parse_only && (!env._optimistic_types || env._lazy_compilation); final String cacheKey = useCodeStore ? CodeStore.getCacheKey("script", null) : null; if (useCodeStore) { storedScript = codeStore.load(source, cacheKey); } if (storedScript == null) { if (env._dest_dir != null) { source.dump(env._dest_dir); } functionNode = new Parser(env, source, errMan, strict, getLogger(Parser.class)).parse(); if (errMan.hasErrors()) { return null; } if (env._print_ast || functionNode.getDebugFlag(FunctionNode.DEBUG_PRINT_AST)) { getErr().println(new ASTWriter(functionNode)); } if (env._print_parse || functionNode.getDebugFlag(FunctionNode.DEBUG_PRINT_PARSE)) { getErr().println(new PrintVisitor(functionNode, true, false)); } } if (env._parse_only) { return null; } final URL url = source.getURL(); final CodeSource cs = new CodeSource(url, (CodeSigner[])null); final CodeInstaller installer; if (!env.useAnonymousClasses(source.getLength()) || env._persistent_cache || !env._lazy_compilation) { // Persistent code cache and eager compilation preclude use of VM anonymous classes final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader; installer = new NamedContextCodeInstaller(this, cs, loader); } else { installer = new AnonymousContextCodeInstaller(this, cs, anonymousHostClasses.getOrCreate(cs, (key) -> createNewLoader().installClass( // NOTE: we're defining these constants in AnonymousContextCodeInstaller so they are not // initialized if we don't use AnonymousContextCodeInstaller. As this method is only ever // invoked from AnonymousContextCodeInstaller, this is okay. AnonymousContextCodeInstaller.ANONYMOUS_HOST_CLASS_NAME, AnonymousContextCodeInstaller.ANONYMOUS_HOST_CLASS_BYTES, cs))); } if (storedScript == null) { final CompilationPhases phases = Compiler.CompilationPhases.COMPILE_ALL; final Compiler compiler = Compiler.forInitialCompilation( installer, source, errMan, strict | functionNode.isStrict()); final FunctionNode compiledFunction = compiler.compile(functionNode, phases); if (errMan.hasErrors()) { return null; } script = compiledFunction.getRootClass(); compiler.persistClassInfo(cacheKey, compiledFunction); } else { Compiler.updateCompilationId(storedScript.getCompilationId()); script = storedScript.installScript(source, installer); } cacheClass(source, script); return script; } private ScriptLoader createNewLoader() { return AccessController.doPrivileged( new PrivilegedAction<ScriptLoader>() { @Override public ScriptLoader run() { return new ScriptLoader(Context.this); } }, CREATE_LOADER_ACC_CTXT); } private long getUniqueScriptId() { return uniqueScriptId.getAndIncrement(); }
Cache for compiled script classes.
/** * Cache for compiled script classes. */
@SuppressWarnings("serial") @Logger(name="classcache") private static class ClassCache extends LinkedHashMap<Source, ClassReference> implements Loggable { private final int size; private final ReferenceQueue<Class<?>> queue; private final DebugLogger log; ClassCache(final Context context, final int size) { super(size, 0.75f, true); this.size = size; this.queue = new ReferenceQueue<>(); this.log = initLogger(context); } void cache(final Source source, final Class<?> clazz) { if (log.isEnabled()) { log.info("Caching ", source, " in class cache"); } put(source, new ClassReference(clazz, queue, source)); } @Override protected boolean removeEldestEntry(final Map.Entry<Source, ClassReference> eldest) { return size() > size; } @Override public ClassReference get(final Object key) { for (ClassReference ref; (ref = (ClassReference)queue.poll()) != null; ) { final Source source = ref.source; if (log.isEnabled()) { log.info("Evicting ", source, " from class cache."); } remove(source); } final ClassReference ref = super.get(key); if (ref != null && log.isEnabled()) { log.info("Retrieved class reference for ", ref.source, " from class cache"); } return ref; } @Override public DebugLogger initLogger(final Context context) { return context.getLogger(getClass()); } @Override public DebugLogger getLogger() { return log; } } private static class ClassReference extends SoftReference<Class<?>> { private final Source source; ClassReference(final Class<?> clazz, final ReferenceQueue<Class<?>> queue, final Source source) { super(clazz, queue); this.source = source; } } // Class cache management private Class<?> findCachedClass(final Source source) { final ClassReference ref = classCache == null ? null : classCache.get(source); return ref != null ? ref.get() : null; } private void cacheClass(final Source source, final Class<?> clazz) { if (classCache != null) { classCache.cache(source, clazz); } } // logging private final Map<String, DebugLogger> loggers = new HashMap<>(); private void initLoggers() { ((Loggable)MethodHandleFactory.getFunctionality()).initLogger(this); }
Get a logger, given a loggable class
Params:
  • clazz – a Loggable class
Returns:debuglogger associated with that class
/** * Get a logger, given a loggable class * @param clazz a Loggable class * @return debuglogger associated with that class */
public DebugLogger getLogger(final Class<? extends Loggable> clazz) { return getLogger(clazz, null); }
Get a logger, given a loggable class
Params:
  • clazz – a Loggable class
  • initHook – an init hook - if this is the first time the logger is created in the context, run the init hook
Returns:debuglogger associated with that class
/** * Get a logger, given a loggable class * @param clazz a Loggable class * @param initHook an init hook - if this is the first time the logger is created in the context, run the init hook * @return debuglogger associated with that class */
public DebugLogger getLogger(final Class<? extends Loggable> clazz, final Consumer<DebugLogger> initHook) { final String name = getLoggerName(clazz); DebugLogger logger = loggers.get(name); if (logger == null) { if (!env.hasLogger(name)) { return DebugLogger.DISABLED_LOGGER; } final LoggerInfo info = env._loggers.get(name); logger = new DebugLogger(name, info.getLevel(), info.isQuiet()); if (initHook != null) { initHook.accept(logger); } loggers.put(name, logger); } return logger; }
Given a Loggable class, weave debug info info a method handle for that logger. Level.INFO is used
Params:
  • clazz – loggable
  • mh – method handle
  • text – debug printout to add
Returns:instrumented method handle, or null if logger not enabled
/** * Given a Loggable class, weave debug info info a method handle for that logger. * Level.INFO is used * * @param clazz loggable * @param mh method handle * @param text debug printout to add * * @return instrumented method handle, or null if logger not enabled */
public MethodHandle addLoggingToHandle(final Class<? extends Loggable> clazz, final MethodHandle mh, final Supplier<String> text) { return addLoggingToHandle(clazz, Level.INFO, mh, Integer.MAX_VALUE, false, text); }
Given a Loggable class, weave debug info info a method handle for that logger.
Params:
  • clazz – loggable
  • level – log level
  • mh – method handle
  • paramStart – first parameter to print
  • printReturnValue – should we print the return value?
  • text – debug printout to add
Returns:instrumented method handle, or null if logger not enabled
/** * Given a Loggable class, weave debug info info a method handle for that logger. * * @param clazz loggable * @param level log level * @param mh method handle * @param paramStart first parameter to print * @param printReturnValue should we print the return value? * @param text debug printout to add * * @return instrumented method handle, or null if logger not enabled */
public MethodHandle addLoggingToHandle(final Class<? extends Loggable> clazz, final Level level, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Supplier<String> text) { final DebugLogger log = getLogger(clazz); if (log.isEnabled()) { return MethodHandleFactory.addDebugPrintout(log, level, mh, paramStart, printReturnValue, text.get()); } return mh; } private static String getLoggerName(final Class<?> clazz) { Class<?> current = clazz; while (current != null) { final Logger log = current.getAnnotation(Logger.class); if (log != null) { assert !"".equals(log.name()); return log.name(); } current = current.getSuperclass(); } assert false; return null; }
This is a special kind of switchpoint used to guard builtin properties and prototypes. In the future it might contain logic to e.g. multiple switchpoint classes.
/** * This is a special kind of switchpoint used to guard builtin * properties and prototypes. In the future it might contain * logic to e.g. multiple switchpoint classes. */
public static final class BuiltinSwitchPoint extends SwitchPoint { //empty }
Create a new builtin switchpoint and return it
Params:
  • name – key name
Returns:new builtin switchpoint
/** * Create a new builtin switchpoint and return it * @param name key name * @return new builtin switchpoint */
public SwitchPoint newBuiltinSwitchPoint(final String name) { assert builtinSwitchPoints.get(name) == null; final SwitchPoint sp = new BuiltinSwitchPoint(); builtinSwitchPoints.put(name, sp); return sp; }
Return the builtin switchpoint for a particular key name
Params:
  • name – key name
Returns:builtin switchpoint or null if none
/** * Return the builtin switchpoint for a particular key name * @param name key name * @return builtin switchpoint or null if none */
public SwitchPoint getBuiltinSwitchPoint(final String name) { return builtinSwitchPoints.get(name); } private static ClassLoader createModuleLoader(final ClassLoader cl, final String modulePath, final String addModules) { if (addModules == null) { throw new IllegalArgumentException("--module-path specified with no --add-modules"); } final Path[] paths = Stream.of(modulePath.split(File.pathSeparator)). map(s -> Paths.get(s)). toArray(sz -> new Path[sz]); final ModuleFinder mf = ModuleFinder.of(paths); final Set<ModuleReference> mrefs = mf.findAll(); if (mrefs.isEmpty()) { throw new RuntimeException("No modules in script --module-path: " + modulePath); } final Set<String> rootMods; if (addModules.equals("ALL-MODULE-PATH")) { rootMods = mrefs.stream(). map(mr->mr.descriptor().name()). collect(Collectors.toSet()); } else { rootMods = Stream.of(addModules.split(",")). map(String::trim). collect(Collectors.toSet()); } final ModuleLayer boot = ModuleLayer.boot(); final Configuration conf = boot.configuration(). resolve(mf, ModuleFinder.of(), rootMods); final String firstMod = rootMods.iterator().next(); return boot.defineModulesWithOneLoader(conf, cl).findLoader(firstMod); } }