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