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