Tue, 14 Oct 2014 16:16:12 +0530
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 }