--- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Context.java 2020-04-15 18:49:44.000000000 +0530 +++ /dev/null 2020-04-15 18:49:44.000000000 +0530 @@ -1,1803 +0,0 @@ -/* - * 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. - */ -public final class Context { - // nashorn specific security runtime access permission names - /** - * 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. - */ - public static final String NASHORN_CREATE_CONTEXT = "nashorn.createContext"; - - /** - * 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. - */ - public static final String NASHORN_GET_CONTEXT = "nashorn.getContext"; - - /** - * Permission to use Java reflection/jsr292 from script code. - */ - public static final String NASHORN_JAVA_REFLECTION = "nashorn.JavaReflection"; - - /** - * 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. - */ - private final FieldMode fieldMode; - - private static enum FieldMode { - /** Value for automatic field representation depending on optimistic types setting */ - AUTO, - /** Value for object field representation regardless of optimistic types setting */ - OBJECTS, - /** 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 - */ - private final Map 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 - */ - 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> classes, final Source source, final Object[] constants) { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @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 classBytes, final Map 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> 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 ? */ - public static final boolean DEBUG = Options.getBooleanProperty("nashorn.debug"); - - private static final ThreadLocal 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 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 - * @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 - * @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 - * @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 - * @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 - * - * @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 - * @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 - * - * @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. */ - private final ScriptEnvironment env; - - /** is this context in strict mode? Cached from env. as this is used heavily. */ - final boolean _strict; - - /** 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. */ - private final ScriptLoader scriptLoader; - - /** Dynamic linker for linking call sites in script code loaded by this context */ - private final DynamicLinker dynamicLinker; - - /** Current error manager. */ - private final ErrorManager errors; - - /** 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. */ - private final ClassFilter classFilter; - - /** Process-wide singleton structure loader */ - private static final StructureLoader theStructLoader; - private static final ConcurrentMap> 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() { - @Override - public StructureLoader run() { - return new StructureLoader(myLoader); - } - }, CREATE_LOADER_ACC_CTXT); - } - - /** - * 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 - * - * @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 - * - * @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 - * - * @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 - * - * @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() { - @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 - * @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. - * @return the factory for constant method handles for global properties. - */ - GlobalConstants getGlobalConstants() { - return globalConstantsRef.get(); - } - - /** - * Get the error manager for this context - * @return error manger - */ - public ErrorManager getErrorManager() { - return errors; - } - - /** - * Get the script environment for this context - * @return script environment - */ - public ScriptEnvironment getEnv() { - return env; - } - - /** - * Get the output stream for this context - * @return output print writer - */ - public PrintWriter getOut() { - return env.getOut(); - } - - /** - * 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? - * @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 - * @return the property map of the current global scope - */ - public static PropertyMap getGlobalMap() { - return Context.getGlobal().getMap(); - } - - /** - * 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 - */ - public static interface MultiGlobalCompiledScript { - /** - * 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. - * - * @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 {@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 {@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 ? "" : 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() { - @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 {@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(); - * } - * })(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 {@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() { - @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 {@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 forStructureClass(final String fullName) throws ClassNotFoundException { - if (System.getSecurityManager() != null && !StructureLoader.isStructureClass(fullName)) { - throw new ClassNotFoundException(fullName); - } - return (Class)structureClasses.computeIfAbsent(fullName, (name) -> { - try { - return Class.forName(name, true, theStructLoader); - } catch (final ClassNotFoundException e) { - throw new AssertionError(e); - } - }); - } - - /** - * 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. - * - * @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. - * - * @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. - * - * @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() { - @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. - * - * @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. - * - * @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 - * {@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 {@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 - * {@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. - * - * @return the initialized global scope object. - */ - public Global createGlobal() { - return initGlobal(newGlobal()); - } - - /** - * 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. - * - * @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. - * - * @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 - * @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. - * @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. - * @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. - * - * @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. - * - * @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 find(final String name) { - if (name.equals(mn)) { - return Optional.of(mref); - } else { - return Optional.empty(); - } - } - @Override - public Set findAll() { - return Set.of(mref); - } - }; - - final Configuration cf = parent.configuration() - .resolve(finder, ModuleFinder.of(), Set.of(mn)); - - final PrivilegedAction 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. - * - * @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() { - @Override - public ScriptLoader run() { - return new ScriptLoader(Context.this); - } - }, CREATE_LOADER_ACC_CTXT); - } - - private long getUniqueScriptId() { - return uniqueScriptId.getAndIncrement(); - } - - /** - * Cache for compiled script classes. - */ - @SuppressWarnings("serial") - @Logger(name="classcache") - private static class ClassCache extends LinkedHashMap implements Loggable { - private final int size; - private final ReferenceQueue> 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 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> { - private final Source source; - - ClassReference(final Class clazz, final ReferenceQueue> 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 loggers = new HashMap<>(); - - private void initLoggers() { - ((Loggable)MethodHandleFactory.getFunctionality()).initLogger(this); - } - - /** - * Get a logger, given a loggable class - * @param clazz a Loggable class - * @return debuglogger associated with that class - */ - public DebugLogger getLogger(final Class clazz) { - return getLogger(clazz, null); - } - - /** - * 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 clazz, final Consumer 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 - * - * @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 clazz, final MethodHandle mh, final Supplier 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. - * - * @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 clazz, final Level level, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Supplier 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. - */ - public static final class BuiltinSwitchPoint extends SwitchPoint { - //empty - } - - /** - * 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 - * @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 mrefs = mf.findAll(); - if (mrefs.isEmpty()) { - throw new RuntimeException("No modules in script --module-path: " + modulePath); - } - - final Set 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); - } -}