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
createPermAccCtxt(final String permName)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
getMessage(final String msgId, final String... args)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 
NashornScriptEngine(final NashornScriptEngineFactory factory, final String[] args, final ClassLoader appLoader, final ClassFilter classFilter)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
eval(final Reader reader, final ScriptContext ctxt)156     public Object eval(final Reader reader, final ScriptContext ctxt) throws ScriptException {
157         return evalImpl(makeSource(reader, ctxt), ctxt);
158     }
159 
160     @Override
eval(final String script, final ScriptContext ctxt)161     public Object eval(final String script, final ScriptContext ctxt) throws ScriptException {
162         return evalImpl(makeSource(script, ctxt), ctxt);
163     }
164 
165     @Override
getFactory()166     public ScriptEngineFactory getFactory() {
167         return factory;
168     }
169 
170     @Override
createBindings()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
compile(final Reader reader)183     public CompiledScript compile(final Reader reader) throws ScriptException {
184         return asCompiledScript(makeSource(reader, context));
185     }
186 
187     @Override
compile(final String str)188     public CompiledScript compile(final String str) throws ScriptException {
189         return asCompiledScript(makeSource(str, context));
190     }
191 
192     // Invocable methods
193 
194     @Override
invokeFunction(final String name, final Object... args)195     public Object invokeFunction(final String name, final Object... args)
196             throws ScriptException, NoSuchMethodException {
197         return invokeImpl(null, name, args);
198     }
199 
200     @Override
invokeMethod(final Object thiz, final String name, final Object... args)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
getInterface(final Class<T> clazz)210     public <T> T getInterface(final Class<T> clazz) {
211         return getInterfaceInner(null, clazz);
212     }
213 
214     @Override
getInterface(final Object thiz, final Class<T> clazz)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 
makeSource(final Reader reader, final ScriptContext ctxt)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 
makeSource(final String src, final ScriptContext ctxt)232     private static Source makeSource(final String src, final ScriptContext ctxt) {
233         return sourceFor(getScriptName(ctxt), src);
234     }
235 
getScriptName(final ScriptContext ctxt)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 
getInterfaceInner(final Object thiz, final Class<T> clazz)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
getNashornGlobalFrom(final ScriptContext ctxt)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
globalFromMirror(final ScriptObjectMirror mirror)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
createGlobalMirror()346     private ScriptObjectMirror createGlobalMirror() {
347         final Global newGlobal = createNashornGlobal();
348         return new ScriptObjectMirror(newGlobal, newGlobal);
349     }
350 
351     // Create a new Nashorn Global object
createNashornGlobal()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 
invokeImpl(final Object selfObject, final String name, final Object... args)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 
evalImpl(final Source src, final ScriptContext ctxt)408     private Object evalImpl(final Source src, final ScriptContext ctxt) throws ScriptException {
409         return evalImpl(compileImpl(src, ctxt), ctxt);
410     }
411 
evalImpl(final ScriptFunction script, final ScriptContext ctxt)412     private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException {
413         return evalImpl(script, ctxt, getNashornGlobalFrom(ctxt));
414     }
415 
evalImpl(final Context.MultiGlobalCompiledScript mgcs, final ScriptContext ctxt, final Global ctxtGlobal)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 
evalImpl(final ScriptFunction script, final ScriptContext ctxt, final Global ctxtGlobal)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 
throwAsScriptException(final Exception e, final Global global)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 
asCompiledScript(final Source source)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 }
577