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