src/jdk/nashorn/api/scripting/NashornScriptEngine.java

Tue, 14 Oct 2014 16:16:12 +0530

author
sundar
date
Tue, 14 Oct 2014 16:16:12 +0530
changeset 1053
a35c8136c045
parent 964
8f2ed41abb26
child 1205
4112748288bb
child 1212
0c0130c5ff1b
permissions
-rw-r--r--

8050977: Java8 Javascript Nashorn exception: no current Global instance for nashorn
Reviewed-by: attila, lagergren, hannesw

     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  */
    26 package jdk.nashorn.api.scripting;
    28 import static jdk.nashorn.internal.runtime.Source.sourceFor;
    30 import java.io.IOException;
    31 import java.io.Reader;
    32 import java.lang.invoke.MethodHandles;
    33 import java.lang.reflect.Method;
    34 import java.lang.reflect.Modifier;
    35 import java.security.AccessControlContext;
    36 import java.security.AccessController;
    37 import java.security.Permissions;
    38 import java.security.PrivilegedAction;
    39 import java.security.ProtectionDomain;
    40 import java.text.MessageFormat;
    41 import java.util.Locale;
    42 import java.util.ResourceBundle;
    43 import javax.script.AbstractScriptEngine;
    44 import javax.script.Bindings;
    45 import javax.script.Compilable;
    46 import javax.script.CompiledScript;
    47 import javax.script.Invocable;
    48 import javax.script.ScriptContext;
    49 import javax.script.ScriptEngine;
    50 import javax.script.ScriptEngineFactory;
    51 import javax.script.ScriptException;
    52 import javax.script.SimpleBindings;
    53 import jdk.nashorn.internal.objects.Global;
    54 import jdk.nashorn.internal.runtime.Context;
    55 import jdk.nashorn.internal.runtime.ErrorManager;
    56 import jdk.nashorn.internal.runtime.ScriptFunction;
    57 import jdk.nashorn.internal.runtime.ScriptObject;
    58 import jdk.nashorn.internal.runtime.ScriptRuntime;
    59 import jdk.nashorn.internal.runtime.Source;
    60 import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
    61 import jdk.nashorn.internal.runtime.options.Options;
    63 /**
    64  * JSR-223 compliant script engine for Nashorn. Instances are not created directly, but rather returned through
    65  * {@link NashornScriptEngineFactory#getScriptEngine()}. Note that this engine implements the {@link Compilable} and
    66  * {@link Invocable} interfaces, allowing for efficient precompilation and repeated execution of scripts.
    67  * @see NashornScriptEngineFactory
    68  */
    70 public final class NashornScriptEngine extends AbstractScriptEngine implements Compilable, Invocable {
    71     /**
    72      * Key used to associate Nashorn global object mirror with arbitrary Bindings instance.
    73      */
    74     public static final String NASHORN_GLOBAL = "nashorn.global";
    76     // commonly used access control context objects
    77     private static AccessControlContext createPermAccCtxt(final String permName) {
    78         final Permissions perms = new Permissions();
    79         perms.add(new RuntimePermission(permName));
    80         return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
    81     }
    83     private static final AccessControlContext CREATE_CONTEXT_ACC_CTXT = createPermAccCtxt(Context.NASHORN_CREATE_CONTEXT);
    84     private static final AccessControlContext CREATE_GLOBAL_ACC_CTXT  = createPermAccCtxt(Context.NASHORN_CREATE_GLOBAL);
    86     // the factory that created this engine
    87     private final ScriptEngineFactory factory;
    88     // underlying nashorn Context - 1:1 with engine instance
    89     private final Context             nashornContext;
    90     // do we want to share single Nashorn global instance across ENGINE_SCOPEs?
    91     private final boolean             _global_per_engine;
    92     // This is the initial default Nashorn global object.
    93     // This is used as "shared" global if above option is true.
    94     private final Global              global;
    96     // Nashorn script engine error message management
    97     private static final String MESSAGES_RESOURCE = "jdk.nashorn.api.scripting.resources.Messages";
    99     private static final ResourceBundle MESSAGES_BUNDLE;
   100     static {
   101         MESSAGES_BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault());
   102     }
   104     // helper to get Nashorn script engine error message
   105     private static String getMessage(final String msgId, final String... args) {
   106         try {
   107             return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args);
   108         } catch (final java.util.MissingResourceException e) {
   109             throw new RuntimeException("no message resource found for message id: "+ msgId);
   110         }
   111     }
   113     NashornScriptEngine(final NashornScriptEngineFactory factory, final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) {
   114         assert args != null : "null argument array";
   115         this.factory = factory;
   116         final Options options = new Options("nashorn");
   117         options.process(args);
   119         // throw ParseException on first error from script
   120         final ErrorManager errMgr = new Context.ThrowErrorManager();
   121         // create new Nashorn Context
   122         this.nashornContext = AccessController.doPrivileged(new PrivilegedAction<Context>() {
   123             @Override
   124             public Context run() {
   125                 try {
   126                     return new Context(options, errMgr, appLoader, classFilter);
   127                 } catch (final RuntimeException e) {
   128                     if (Context.DEBUG) {
   129                         e.printStackTrace();
   130                     }
   131                     throw e;
   132                 }
   133             }
   134         }, CREATE_CONTEXT_ACC_CTXT);
   136         // cache this option that is used often
   137         this._global_per_engine = nashornContext.getEnv()._global_per_engine;
   139         // create new global object
   140         this.global = createNashornGlobal(context);
   141         // set the default ENGINE_SCOPE object for the default context
   142         context.setBindings(new ScriptObjectMirror(global, global), ScriptContext.ENGINE_SCOPE);
   143     }
   145     @Override
   146     public Object eval(final Reader reader, final ScriptContext ctxt) throws ScriptException {
   147         return evalImpl(makeSource(reader, ctxt), ctxt);
   148     }
   150     @Override
   151     public Object eval(final String script, final ScriptContext ctxt) throws ScriptException {
   152         return evalImpl(makeSource(script, ctxt), ctxt);
   153     }
   155     @Override
   156     public ScriptEngineFactory getFactory() {
   157         return factory;
   158     }
   160     @Override
   161     public Bindings createBindings() {
   162         if (_global_per_engine) {
   163             // just create normal SimpleBindings.
   164             // We use same 'global' for all Bindings.
   165             return new SimpleBindings();
   166         }
   167         return createGlobalMirror(null);
   168     }
   170     // Compilable methods
   172     @Override
   173     public CompiledScript compile(final Reader reader) throws ScriptException {
   174         return asCompiledScript(makeSource(reader, context));
   175     }
   177     @Override
   178     public CompiledScript compile(final String str) throws ScriptException {
   179         return asCompiledScript(makeSource(str, context));
   180     }
   182     // Invocable methods
   184     @Override
   185     public Object invokeFunction(final String name, final Object... args)
   186             throws ScriptException, NoSuchMethodException {
   187         return invokeImpl(null, name, args);
   188     }
   190     @Override
   191     public Object invokeMethod(final Object thiz, final String name, final Object... args)
   192             throws ScriptException, NoSuchMethodException {
   193         if (thiz == null) {
   194             throw new IllegalArgumentException(getMessage("thiz.cannot.be.null"));
   195         }
   196         return invokeImpl(thiz, name, args);
   197     }
   199     @Override
   200     public <T> T getInterface(final Class<T> clazz) {
   201         return getInterfaceInner(null, clazz);
   202     }
   204     @Override
   205     public <T> T getInterface(final Object thiz, final Class<T> clazz) {
   206         if (thiz == null) {
   207             throw new IllegalArgumentException(getMessage("thiz.cannot.be.null"));
   208         }
   209         return getInterfaceInner(thiz, clazz);
   210     }
   212     // Implementation only below this point
   214     private static Source makeSource(final Reader reader, final ScriptContext ctxt) throws ScriptException {
   215         try {
   216             return sourceFor(getScriptName(ctxt), reader);
   217         } catch (final IOException e) {
   218             throw new ScriptException(e);
   219         }
   220     }
   222     private static Source makeSource(final String src, final ScriptContext ctxt) {
   223         return sourceFor(getScriptName(ctxt), src);
   224     }
   226     private static String getScriptName(final ScriptContext ctxt) {
   227         final Object val = ctxt.getAttribute(ScriptEngine.FILENAME);
   228         return (val != null) ? val.toString() : "<eval>";
   229     }
   231     private <T> T getInterfaceInner(final Object thiz, final Class<T> clazz) {
   232         assert !(thiz instanceof ScriptObject) : "raw ScriptObject not expected here";
   234         if (clazz == null || !clazz.isInterface()) {
   235             throw new IllegalArgumentException(getMessage("interface.class.expected"));
   236         }
   238         // perform security access check as early as possible
   239         final SecurityManager sm = System.getSecurityManager();
   240         if (sm != null) {
   241             if (! Modifier.isPublic(clazz.getModifiers())) {
   242                 throw new SecurityException(getMessage("implementing.non.public.interface", clazz.getName()));
   243             }
   244             Context.checkPackageAccess(clazz);
   245         }
   247         ScriptObject realSelf = null;
   248         Global realGlobal = null;
   249         if(thiz == null) {
   250             // making interface out of global functions
   251             realSelf = realGlobal = getNashornGlobalFrom(context);
   252         } else if (thiz instanceof ScriptObjectMirror) {
   253             final ScriptObjectMirror mirror = (ScriptObjectMirror)thiz;
   254             realSelf = mirror.getScriptObject();
   255             realGlobal = mirror.getHomeGlobal();
   256             if (! isOfContext(realGlobal, nashornContext)) {
   257                 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
   258             }
   259         }
   261         if (realSelf == null) {
   262             throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
   263         }
   265         try {
   266             final Global oldGlobal = Context.getGlobal();
   267             final boolean globalChanged = (oldGlobal != realGlobal);
   268             try {
   269                 if (globalChanged) {
   270                     Context.setGlobal(realGlobal);
   271                 }
   273                 if (! isInterfaceImplemented(clazz, realSelf)) {
   274                     return null;
   275                 }
   276                 return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz,
   277                         MethodHandles.publicLookup()).invoke(realSelf));
   278             } finally {
   279                 if (globalChanged) {
   280                     Context.setGlobal(oldGlobal);
   281                 }
   282             }
   283         } catch(final RuntimeException|Error e) {
   284             throw e;
   285         } catch(final Throwable t) {
   286             throw new RuntimeException(t);
   287         }
   288     }
   290     // Retrieve nashorn Global object for a given ScriptContext object
   291     private Global getNashornGlobalFrom(final ScriptContext ctxt) {
   292         if (_global_per_engine) {
   293             // shared single global object for all ENGINE_SCOPE Bindings
   294             return global;
   295         }
   297         final Bindings bindings = ctxt.getBindings(ScriptContext.ENGINE_SCOPE);
   298         // is this Nashorn's own Bindings implementation?
   299         if (bindings instanceof ScriptObjectMirror) {
   300             final Global glob = globalFromMirror((ScriptObjectMirror)bindings);
   301             if (glob != null) {
   302                 return glob;
   303             }
   304         }
   306         // Arbitrary user Bindings implementation. Look for NASHORN_GLOBAL in it!
   307         final Object scope = bindings.get(NASHORN_GLOBAL);
   308         if (scope instanceof ScriptObjectMirror) {
   309             final Global glob = globalFromMirror((ScriptObjectMirror)scope);
   310             if (glob != null) {
   311                 return glob;
   312             }
   313         }
   315         // We didn't find associated nashorn global mirror in the Bindings given!
   316         // Create new global instance mirror and associate with the Bindings.
   317         final ScriptObjectMirror mirror = createGlobalMirror(ctxt);
   318         bindings.put(NASHORN_GLOBAL, mirror);
   319         return mirror.getHomeGlobal();
   320     }
   322     // Retrieve nashorn Global object from a given ScriptObjectMirror
   323     private Global globalFromMirror(final ScriptObjectMirror mirror) {
   324         final ScriptObject sobj = mirror.getScriptObject();
   325         if (sobj instanceof Global && isOfContext((Global)sobj, nashornContext)) {
   326             return (Global)sobj;
   327         }
   329         return null;
   330     }
   332     // Create a new ScriptObjectMirror wrapping a newly created Nashorn Global object
   333     private ScriptObjectMirror createGlobalMirror(final ScriptContext ctxt) {
   334         final Global newGlobal = createNashornGlobal(ctxt);
   335         return new ScriptObjectMirror(newGlobal, newGlobal);
   336     }
   338     // Create a new Nashorn Global object
   339     private Global createNashornGlobal(final ScriptContext ctxt) {
   340         final Global newGlobal = AccessController.doPrivileged(new PrivilegedAction<Global>() {
   341             @Override
   342             public Global run() {
   343                 try {
   344                     return nashornContext.newGlobal();
   345                 } catch (final RuntimeException e) {
   346                     if (Context.DEBUG) {
   347                         e.printStackTrace();
   348                     }
   349                     throw e;
   350                 }
   351             }
   352         }, CREATE_GLOBAL_ACC_CTXT);
   354         nashornContext.initGlobal(newGlobal, this);
   355         newGlobal.setScriptContext(ctxt);
   357         return newGlobal;
   358     }
   360     private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
   361         name.getClass(); // null check
   362         assert !(selfObject instanceof ScriptObject) : "raw ScriptObject not expected here";
   364         Global invokeGlobal = null;
   365         ScriptObjectMirror selfMirror = null;
   366         if (selfObject instanceof ScriptObjectMirror) {
   367             selfMirror = (ScriptObjectMirror)selfObject;
   368             if (! isOfContext(selfMirror.getHomeGlobal(), nashornContext)) {
   369                 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
   370             }
   371             invokeGlobal = selfMirror.getHomeGlobal();
   372         } else if (selfObject == null) {
   373             // selfObject is null => global function call
   374             final Global ctxtGlobal = getNashornGlobalFrom(context);
   375             invokeGlobal = ctxtGlobal;
   376             selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(ctxtGlobal, ctxtGlobal);
   377         }
   379         if (selfMirror != null) {
   380             try {
   381                 return ScriptObjectMirror.translateUndefined(selfMirror.callMember(name, args));
   382             } catch (final Exception e) {
   383                 final Throwable cause = e.getCause();
   384                 if (cause instanceof NoSuchMethodException) {
   385                     throw (NoSuchMethodException)cause;
   386                 }
   387                 throwAsScriptException(e, invokeGlobal);
   388                 throw new AssertionError("should not reach here");
   389             }
   390         }
   392         // Non-script object passed as selfObject
   393         throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
   394     }
   396     private Object evalImpl(final Source src, final ScriptContext ctxt) throws ScriptException {
   397         return evalImpl(compileImpl(src, ctxt), ctxt);
   398     }
   400     private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException {
   401         return evalImpl(script, ctxt, getNashornGlobalFrom(ctxt));
   402     }
   404     private static Object evalImpl(final Context.MultiGlobalCompiledScript mgcs, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException {
   405         final Global oldGlobal = Context.getGlobal();
   406         final boolean globalChanged = (oldGlobal != ctxtGlobal);
   407         try {
   408             if (globalChanged) {
   409                 Context.setGlobal(ctxtGlobal);
   410             }
   412             final ScriptFunction script = mgcs.getFunction(ctxtGlobal);
   413             ctxtGlobal.setScriptContext(ctxt);
   414             return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal));
   415         } catch (final Exception e) {
   416             throwAsScriptException(e, ctxtGlobal);
   417             throw new AssertionError("should not reach here");
   418         } finally {
   419             if (globalChanged) {
   420                 Context.setGlobal(oldGlobal);
   421             }
   422         }
   423     }
   425     private static Object evalImpl(final ScriptFunction script, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException {
   426         if (script == null) {
   427             return null;
   428         }
   429         final Global oldGlobal = Context.getGlobal();
   430         final boolean globalChanged = (oldGlobal != ctxtGlobal);
   431         try {
   432             if (globalChanged) {
   433                 Context.setGlobal(ctxtGlobal);
   434             }
   436             ctxtGlobal.setScriptContext(ctxt);
   437             return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal));
   438         } catch (final Exception e) {
   439             throwAsScriptException(e, ctxtGlobal);
   440             throw new AssertionError("should not reach here");
   441         } finally {
   442             if (globalChanged) {
   443                 Context.setGlobal(oldGlobal);
   444             }
   445         }
   446     }
   448     private static void throwAsScriptException(final Exception e, final Global global) throws ScriptException {
   449         if (e instanceof ScriptException) {
   450             throw (ScriptException)e;
   451         } else if (e instanceof NashornException) {
   452             final NashornException ne = (NashornException)e;
   453             final ScriptException se = new ScriptException(
   454                 ne.getMessage(), ne.getFileName(),
   455                 ne.getLineNumber(), ne.getColumnNumber());
   456             ne.initEcmaError(global);
   457             se.initCause(e);
   458             throw se;
   459         } else if (e instanceof RuntimeException) {
   460             throw (RuntimeException)e;
   461         } else {
   462             // wrap any other exception as ScriptException
   463             throw new ScriptException(e);
   464         }
   465     }
   467     private CompiledScript asCompiledScript(final Source source) throws ScriptException {
   468         final Context.MultiGlobalCompiledScript mgcs;
   469         final ScriptFunction func;
   470         final Global oldGlobal = Context.getGlobal();
   471         final Global newGlobal = getNashornGlobalFrom(context);
   472         final boolean globalChanged = (oldGlobal != newGlobal);
   473         try {
   474             if (globalChanged) {
   475                 Context.setGlobal(newGlobal);
   476             }
   478             mgcs = nashornContext.compileScript(source);
   479             func = mgcs.getFunction(newGlobal);
   480         } catch (final Exception e) {
   481             throwAsScriptException(e, newGlobal);
   482             throw new AssertionError("should not reach here");
   483         } finally {
   484             if (globalChanged) {
   485                 Context.setGlobal(oldGlobal);
   486             }
   487         }
   489         return new CompiledScript() {
   490             @Override
   491             public Object eval(final ScriptContext ctxt) throws ScriptException {
   492                 final Global globalObject = getNashornGlobalFrom(ctxt);
   493                 // Are we running the script in the same global in which it was compiled?
   494                 if (func.getScope() == globalObject) {
   495                     return evalImpl(func, ctxt, globalObject);
   496                 }
   498                 // different global
   499                 return evalImpl(mgcs, ctxt, globalObject);
   500             }
   501             @Override
   502             public ScriptEngine getEngine() {
   503                 return NashornScriptEngine.this;
   504             }
   505         };
   506     }
   508     private ScriptFunction compileImpl(final Source source, final ScriptContext ctxt) throws ScriptException {
   509         return compileImpl(source, getNashornGlobalFrom(ctxt));
   510     }
   512     private ScriptFunction compileImpl(final Source source, final Global newGlobal) throws ScriptException {
   513         final Global oldGlobal = Context.getGlobal();
   514         final boolean globalChanged = (oldGlobal != newGlobal);
   515         try {
   516             if (globalChanged) {
   517                 Context.setGlobal(newGlobal);
   518             }
   520             return nashornContext.compileScript(source, newGlobal);
   521         } catch (final Exception e) {
   522             throwAsScriptException(e, newGlobal);
   523             throw new AssertionError("should not reach here");
   524         } finally {
   525             if (globalChanged) {
   526                 Context.setGlobal(oldGlobal);
   527             }
   528         }
   529     }
   531     private static boolean isInterfaceImplemented(final Class<?> iface, final ScriptObject sobj) {
   532         for (final Method method : iface.getMethods()) {
   533             // ignore methods of java.lang.Object class
   534             if (method.getDeclaringClass() == Object.class) {
   535                 continue;
   536             }
   538             // skip check for default methods - non-abstract, interface methods
   539             if (! Modifier.isAbstract(method.getModifiers())) {
   540                 continue;
   541             }
   543             final Object obj = sobj.get(method.getName());
   544             if (! (obj instanceof ScriptFunction)) {
   545                 return false;
   546             }
   547         }
   548         return true;
   549     }
   551     private static boolean isOfContext(final Global global, final Context context) {
   552         return global.isOfContext(context);
   553     }
   554 }

mercurial