40 import java.security.AccessControlContext;
41 import java.security.AccessController;
42 import java.security.Permissions;
43 import java.security.PrivilegedAction;
44 import java.security.PrivilegedActionException;
45 import java.security.PrivilegedExceptionAction;
46 import java.security.ProtectionDomain;
47 import java.text.MessageFormat;
48 import java.util.Locale;
49 import java.util.ResourceBundle;
50 import javax.script.AbstractScriptEngine;
51 import javax.script.Bindings;
52 import javax.script.Compilable;
53 import javax.script.CompiledScript;
54 import javax.script.Invocable;
55 import javax.script.ScriptContext;
56 import javax.script.ScriptEngine;
57 import javax.script.ScriptEngineFactory;
58 import javax.script.ScriptException;
59 import javax.script.SimpleBindings;
60 import jdk.nashorn.internal.runtime.Context;
61 import jdk.nashorn.internal.runtime.ErrorManager;
62 import jdk.nashorn.internal.runtime.GlobalObject;
63 import jdk.nashorn.internal.runtime.Property;
64 import jdk.nashorn.internal.runtime.ScriptFunction;
65 import jdk.nashorn.internal.runtime.ScriptObject;
66 import jdk.nashorn.internal.runtime.ScriptRuntime;
67 import jdk.nashorn.internal.runtime.Source;
68 import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
69 import jdk.nashorn.internal.runtime.options.Options;
70
71 /**
72 * JSR-223 compliant script engine for Nashorn. Instances are not created directly, but rather returned through
73 * {@link NashornScriptEngineFactory#getScriptEngine()}. Note that this engine implements the {@link Compilable} and
74 * {@link Invocable} interfaces, allowing for efficient precompilation and repeated execution of scripts.
75 * @see NashornScriptEngineFactory
76 */
77
78 public final class NashornScriptEngine extends AbstractScriptEngine implements Compilable, Invocable {
79 /**
80 * Key used to associate Nashorn global object mirror with arbitrary Bindings instance.
81 */
82 public static final String NASHORN_GLOBAL = "nashorn.global";
83
84 // commonly used access control context objects
85 private static AccessControlContext createPermAccCtxt(final String permName) {
86 final Permissions perms = new Permissions();
87 perms.add(new RuntimePermission(permName));
88 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
89 }
90
91 private static final AccessControlContext CREATE_CONTEXT_ACC_CTXT = createPermAccCtxt(Context.NASHORN_CREATE_CONTEXT);
92 private static final AccessControlContext CREATE_GLOBAL_ACC_CTXT = createPermAccCtxt(Context.NASHORN_CREATE_GLOBAL);
93
94 // the factory that created this engine
95 private final ScriptEngineFactory factory;
96 // underlying nashorn Context - 1:1 with engine instance
97 private final Context nashornContext;
98 // do we want to share single Nashorn global instance across ENGINE_SCOPEs?
99 private final boolean _global_per_engine;
100 // This is the initial default Nashorn global object.
101 // This is used as "shared" global if above option is true.
102 private final ScriptObject global;
103 // initialized bit late to be made 'final'.
104 // Property object for "context" property of global object.
105 private volatile Property contextProperty;
106
107 // default options passed to Nashorn Options object
108 private static final String[] DEFAULT_OPTIONS = new String[] { "-doe" };
109
110 // Nashorn script engine error message management
111 private static final String MESSAGES_RESOURCE = "jdk.nashorn.api.scripting.resources.Messages";
112
113 private static final ResourceBundle MESSAGES_BUNDLE;
114 static {
115 MESSAGES_BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault());
116 }
117
118 // helper to get Nashorn script engine error message
119 private static String getMessage(final String msgId, final String... args) {
120 try {
121 return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args);
122 } catch (final java.util.MissingResourceException e) {
247 public <T> T getInterface(final Object thiz, final Class<T> clazz) {
248 if (thiz == null) {
249 throw new IllegalArgumentException(getMessage("thiz.cannot.be.null"));
250 }
251 return getInterfaceInner(thiz, clazz);
252 }
253
254 // These are called from the "engine.js" script
255
256 /**
257 * This hook is used to search js global variables exposed from Java code.
258 *
259 * @param self 'this' passed from the script
260 * @param ctxt current ScriptContext in which name is searched
261 * @param name name of the variable searched
262 * @return the value of the named variable
263 */
264 public Object __noSuchProperty__(final Object self, final ScriptContext ctxt, final String name) {
265 if (ctxt != null) {
266 final int scope = ctxt.getAttributesScope(name);
267 final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
268 if (scope != -1) {
269 return ScriptObjectMirror.unwrap(ctxt.getAttribute(name, scope), ctxtGlobal);
270 }
271
272 if (self == UNDEFINED) {
273 // scope access and so throw ReferenceError
274 throw referenceError(ctxtGlobal, "not.defined", name);
275 }
276 }
277
278 return UNDEFINED;
279 }
280
281 // Implementation only below this point
282
283 private static Source makeSource(final Reader reader, final ScriptContext ctxt) throws ScriptException {
284 try {
285 if (reader instanceof URLReader) {
286 final URL url = ((URLReader)reader).getURL();
287 final Charset cs = ((URLReader)reader).getCharset();
300 private static String getScriptName(final ScriptContext ctxt) {
301 final Object val = ctxt.getAttribute(ScriptEngine.FILENAME);
302 return (val != null) ? val.toString() : "<eval>";
303 }
304
305 private <T> T getInterfaceInner(final Object thiz, final Class<T> clazz) {
306 if (clazz == null || !clazz.isInterface()) {
307 throw new IllegalArgumentException(getMessage("interface.class.expected"));
308 }
309
310 // perform security access check as early as possible
311 final SecurityManager sm = System.getSecurityManager();
312 if (sm != null) {
313 if (! Modifier.isPublic(clazz.getModifiers())) {
314 throw new SecurityException(getMessage("implementing.non.public.interface", clazz.getName()));
315 }
316 Context.checkPackageAccess(clazz);
317 }
318
319 ScriptObject realSelf = null;
320 ScriptObject realGlobal = null;
321 if(thiz == null) {
322 // making interface out of global functions
323 realSelf = realGlobal = getNashornGlobalFrom(context);
324 } else if (thiz instanceof ScriptObjectMirror) {
325 final ScriptObjectMirror mirror = (ScriptObjectMirror)thiz;
326 realSelf = mirror.getScriptObject();
327 realGlobal = mirror.getHomeGlobal();
328 if (! isOfContext(realGlobal, nashornContext)) {
329 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
330 }
331 } else if (thiz instanceof ScriptObject) {
332 // called from script code.
333 realSelf = (ScriptObject)thiz;
334 realGlobal = Context.getGlobal();
335 if (realGlobal == null) {
336 throw new IllegalArgumentException(getMessage("no.current.nashorn.global"));
337 }
338
339 if (! isOfContext(realGlobal, nashornContext)) {
340 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
341 }
342 }
343
344 if (realSelf == null) {
345 throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
346 }
347
348 try {
349 final ScriptObject oldGlobal = Context.getGlobal();
350 final boolean globalChanged = (oldGlobal != realGlobal);
351 try {
352 if (globalChanged) {
353 Context.setGlobal(realGlobal);
354 }
355
356 if (! isInterfaceImplemented(clazz, realSelf)) {
357 return null;
358 }
359 return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz,
360 MethodHandles.publicLookup()).invoke(realSelf));
361 } finally {
362 if (globalChanged) {
363 Context.setGlobal(oldGlobal);
364 }
365 }
366 } catch(final RuntimeException|Error e) {
367 throw e;
368 } catch(final Throwable t) {
369 throw new RuntimeException(t);
370 }
371 }
372
373 // Retrieve nashorn Global object for a given ScriptContext object
374 private ScriptObject getNashornGlobalFrom(final ScriptContext ctxt) {
375 if (_global_per_engine) {
376 // shared single global object for all ENGINE_SCOPE Bindings
377 return global;
378 }
379
380 final Bindings bindings = ctxt.getBindings(ScriptContext.ENGINE_SCOPE);
381 // is this Nashorn's own Bindings implementation?
382 if (bindings instanceof ScriptObjectMirror) {
383 final ScriptObject sobj = globalFromMirror((ScriptObjectMirror)bindings);
384 if (sobj != null) {
385 return sobj;
386 }
387 }
388
389 // Arbitrary user Bindings implementation. Look for NASHORN_GLOBAL in it!
390 Object scope = bindings.get(NASHORN_GLOBAL);
391 if (scope instanceof ScriptObjectMirror) {
392 final ScriptObject sobj = globalFromMirror((ScriptObjectMirror)scope);
393 if (sobj != null) {
394 return sobj;
395 }
396 }
397
398 // We didn't find associated nashorn global mirror in the Bindings given!
399 // Create new global instance mirror and associate with the Bindings.
400 final ScriptObjectMirror mirror = createGlobalMirror(ctxt);
401 bindings.put(NASHORN_GLOBAL, mirror);
402 return mirror.getScriptObject();
403 }
404
405 // Retrieve nashorn Global object from a given ScriptObjectMirror
406 private ScriptObject globalFromMirror(final ScriptObjectMirror mirror) {
407 ScriptObject sobj = mirror.getScriptObject();
408 if (sobj instanceof GlobalObject && isOfContext(sobj, nashornContext)) {
409 return sobj;
410 }
411
412 return null;
413 }
414
415 // Create a new ScriptObjectMirror wrapping a newly created Nashorn Global object
416 private ScriptObjectMirror createGlobalMirror(final ScriptContext ctxt) {
417 final ScriptObject newGlobal = createNashornGlobal(ctxt);
418 return new ScriptObjectMirror(newGlobal, newGlobal);
419 }
420
421 // Create a new Nashorn Global object
422 private ScriptObject createNashornGlobal(final ScriptContext ctxt) {
423 final ScriptObject newGlobal = AccessController.doPrivileged(new PrivilegedAction<ScriptObject>() {
424 @Override
425 public ScriptObject run() {
426 try {
427 return nashornContext.newGlobal();
428 } catch (final RuntimeException e) {
429 if (Context.DEBUG) {
430 e.printStackTrace();
431 }
432 throw e;
433 }
434 }
435 }, CREATE_GLOBAL_ACC_CTXT);
436
437 nashornContext.initGlobal(newGlobal);
438
439 final int NON_ENUMERABLE_CONSTANT = Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE | Property.NOT_WRITABLE;
440 // current ScriptContext exposed as "context"
441 // "context" is non-writable from script - but script engine still
442 // needs to set it and so save the context Property object
443 contextProperty = newGlobal.addOwnProperty("context", NON_ENUMERABLE_CONSTANT, ctxt);
444 // current ScriptEngine instance exposed as "engine". We added @SuppressWarnings("LeakingThisInConstructor") as
445 // NetBeans identifies this assignment as such a leak - this is a false positive as we're setting this property
446 // in the Global of a Context we just created - both the Context and the Global were just created and can not be
447 // seen from another thread outside of this constructor.
448 newGlobal.addOwnProperty("engine", NON_ENUMERABLE_CONSTANT, this);
449 // global script arguments with undefined value
450 newGlobal.addOwnProperty("arguments", Property.NOT_ENUMERABLE, UNDEFINED);
451 // file name default is null
452 newGlobal.addOwnProperty(ScriptEngine.FILENAME, Property.NOT_ENUMERABLE, null);
453 // evaluate engine.js initialization script this new global object
454 try {
455 evalImpl(compileImpl(ENGINE_SCRIPT_SRC, newGlobal), ctxt, newGlobal);
456 } catch (final ScriptException exp) {
457 throw new RuntimeException(exp);
458 }
459 return newGlobal;
460 }
461
462 // scripts should see "context" and "engine" as variables in the given global object
463 private void setContextVariables(final ScriptObject ctxtGlobal, final ScriptContext ctxt) {
464 // set "context" global variable via contextProperty - because this
465 // property is non-writable
466 contextProperty.setObjectValue(ctxtGlobal, ctxtGlobal, ctxt, false);
467 Object args = ScriptObjectMirror.unwrap(ctxt.getAttribute("arguments"), ctxtGlobal);
468 if (args == null || args == UNDEFINED) {
469 args = ScriptRuntime.EMPTY_ARRAY;
470 }
471 // if no arguments passed, expose it
472 if (! (args instanceof ScriptObject)) {
473 args = ((GlobalObject)ctxtGlobal).wrapAsObject(args);
474 ctxtGlobal.set("arguments", args, false);
475 }
476 }
477
478 private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
479 name.getClass(); // null check
480
481 ScriptObjectMirror selfMirror = null;
482 if (selfObject instanceof ScriptObjectMirror) {
483 selfMirror = (ScriptObjectMirror)selfObject;
484 if (! isOfContext(selfMirror.getHomeGlobal(), nashornContext)) {
485 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
486 }
487 } else if (selfObject instanceof ScriptObject) {
488 // invokeMethod called from script code - in which case we may get 'naked' ScriptObject
489 // Wrap it with oldGlobal to make a ScriptObjectMirror for the same.
490 final ScriptObject oldGlobal = Context.getGlobal();
491 if (oldGlobal == null) {
492 throw new IllegalArgumentException(getMessage("no.current.nashorn.global"));
493 }
494
495 if (! isOfContext(oldGlobal, nashornContext)) {
496 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
497 }
498
499 selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(selfObject, oldGlobal);
500 } else if (selfObject == null) {
501 // selfObject is null => global function call
502 final ScriptObject ctxtGlobal = getNashornGlobalFrom(context);
503 selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(ctxtGlobal, ctxtGlobal);
504 }
505
506 if (selfMirror != null) {
507 try {
508 return ScriptObjectMirror.translateUndefined(selfMirror.callMember(name, args));
509 } catch (final Exception e) {
510 final Throwable cause = e.getCause();
511 if (cause instanceof NoSuchMethodException) {
512 throw (NoSuchMethodException)cause;
513 }
514 throwAsScriptException(e);
515 throw new AssertionError("should not reach here");
516 }
517 }
518
519 // Non-script object passed as selfObject
520 throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
521 }
522
523 private Object evalImpl(final Source src, final ScriptContext ctxt) throws ScriptException {
524 return evalImpl(compileImpl(src, ctxt), ctxt);
525 }
526
527 private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException {
528 return evalImpl(script, ctxt, getNashornGlobalFrom(ctxt));
529 }
530
531 private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt, final ScriptObject ctxtGlobal) throws ScriptException {
532 if (script == null) {
533 return null;
534 }
535 final ScriptObject oldGlobal = Context.getGlobal();
536 final boolean globalChanged = (oldGlobal != ctxtGlobal);
537 try {
538 if (globalChanged) {
539 Context.setGlobal(ctxtGlobal);
540 }
541
542 // set ScriptContext variables if ctxt is non-null
543 if (ctxt != null) {
544 setContextVariables(ctxtGlobal, ctxt);
545 }
546 return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal));
547 } catch (final Exception e) {
548 throwAsScriptException(e);
549 throw new AssertionError("should not reach here");
550 } finally {
551 if (globalChanged) {
552 Context.setGlobal(oldGlobal);
553 }
554 }
555 }
556
557 private static void throwAsScriptException(final Exception e) throws ScriptException {
558 if (e instanceof ScriptException) {
559 throw (ScriptException)e;
560 } else if (e instanceof NashornException) {
561 final NashornException ne = (NashornException)e;
562 final ScriptException se = new ScriptException(
563 ne.getMessage(), ne.getFileName(),
564 ne.getLineNumber(), ne.getColumnNumber());
565 se.initCause(e);
566 throw se;
567 } else if (e instanceof RuntimeException) {
568 throw (RuntimeException)e;
569 } else {
570 // wrap any other exception as ScriptException
571 throw new ScriptException(e);
572 }
573 }
574
575 private CompiledScript asCompiledScript(final Source source) throws ScriptException {
576 final ScriptFunction func = compileImpl(source, context);
577 return new CompiledScript() {
578 @Override
579 public Object eval(final ScriptContext ctxt) throws ScriptException {
580 final ScriptObject globalObject = getNashornGlobalFrom(ctxt);
581 // Are we running the script in the correct global?
582 if (func.getScope() == globalObject) {
583 return evalImpl(func, ctxt, globalObject);
584 }
585 // ScriptContext with a different global. Compile again!
586 // Note that we may still hit per-global compilation cache.
587 return evalImpl(compileImpl(source, ctxt), ctxt, globalObject);
588 }
589 @Override
590 public ScriptEngine getEngine() {
591 return NashornScriptEngine.this;
592 }
593 };
594 }
595
596 private ScriptFunction compileImpl(final Source source, final ScriptContext ctxt) throws ScriptException {
597 return compileImpl(source, getNashornGlobalFrom(ctxt));
598 }
599
600 private ScriptFunction compileImpl(final Source source, final ScriptObject newGlobal) throws ScriptException {
601 final ScriptObject oldGlobal = Context.getGlobal();
602 final boolean globalChanged = (oldGlobal != newGlobal);
603 try {
604 if (globalChanged) {
605 Context.setGlobal(newGlobal);
606 }
607
608 return nashornContext.compileScript(source, newGlobal);
609 } catch (final Exception e) {
610 throwAsScriptException(e);
611 throw new AssertionError("should not reach here");
612 } finally {
613 if (globalChanged) {
614 Context.setGlobal(oldGlobal);
615 }
616 }
617 }
618
619 private static boolean isInterfaceImplemented(final Class<?> iface, final ScriptObject sobj) {
620 for (final Method method : iface.getMethods()) {
621 // ignore methods of java.lang.Object class
622 if (method.getDeclaringClass() == Object.class) {
623 continue;
624 }
625
626 Object obj = sobj.get(method.getName());
627 if (! (obj instanceof ScriptFunction)) {
628 return false;
629 }
630 }
631 return true;
632 }
633
634 private static boolean isOfContext(final ScriptObject global, final Context context) {
635 assert global instanceof GlobalObject: "Not a Global object";
636 return ((GlobalObject)global).isOfContext(context);
637 }
638 }
|
40 import java.security.AccessControlContext;
41 import java.security.AccessController;
42 import java.security.Permissions;
43 import java.security.PrivilegedAction;
44 import java.security.PrivilegedActionException;
45 import java.security.PrivilegedExceptionAction;
46 import java.security.ProtectionDomain;
47 import java.text.MessageFormat;
48 import java.util.Locale;
49 import java.util.ResourceBundle;
50 import javax.script.AbstractScriptEngine;
51 import javax.script.Bindings;
52 import javax.script.Compilable;
53 import javax.script.CompiledScript;
54 import javax.script.Invocable;
55 import javax.script.ScriptContext;
56 import javax.script.ScriptEngine;
57 import javax.script.ScriptEngineFactory;
58 import javax.script.ScriptException;
59 import javax.script.SimpleBindings;
60 import jdk.nashorn.internal.objects.Global;
61 import jdk.nashorn.internal.runtime.Context;
62 import jdk.nashorn.internal.runtime.ErrorManager;
63 import jdk.nashorn.internal.runtime.Property;
64 import jdk.nashorn.internal.runtime.ScriptFunction;
65 import jdk.nashorn.internal.runtime.ScriptObject;
66 import jdk.nashorn.internal.runtime.ScriptRuntime;
67 import jdk.nashorn.internal.runtime.Source;
68 import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
69 import jdk.nashorn.internal.runtime.options.Options;
70
71 /**
72 * JSR-223 compliant script engine for Nashorn. Instances are not created directly, but rather returned through
73 * {@link NashornScriptEngineFactory#getScriptEngine()}. Note that this engine implements the {@link Compilable} and
74 * {@link Invocable} interfaces, allowing for efficient precompilation and repeated execution of scripts.
75 * @see NashornScriptEngineFactory
76 */
77
78 public final class NashornScriptEngine extends AbstractScriptEngine implements Compilable, Invocable {
79 /**
80 * Key used to associate Nashorn global object mirror with arbitrary Bindings instance.
81 */
82 public static final String NASHORN_GLOBAL = "nashorn.global";
83
84 // commonly used access control context objects
85 private static AccessControlContext createPermAccCtxt(final String permName) {
86 final Permissions perms = new Permissions();
87 perms.add(new RuntimePermission(permName));
88 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
89 }
90
91 private static final AccessControlContext CREATE_CONTEXT_ACC_CTXT = createPermAccCtxt(Context.NASHORN_CREATE_CONTEXT);
92 private static final AccessControlContext CREATE_GLOBAL_ACC_CTXT = createPermAccCtxt(Context.NASHORN_CREATE_GLOBAL);
93
94 // the factory that created this engine
95 private final ScriptEngineFactory factory;
96 // underlying nashorn Context - 1:1 with engine instance
97 private final Context nashornContext;
98 // do we want to share single Nashorn global instance across ENGINE_SCOPEs?
99 private final boolean _global_per_engine;
100 // This is the initial default Nashorn global object.
101 // This is used as "shared" global if above option is true.
102 private final Global global;
103 // initialized bit late to be made 'final'.
104 // Property object for "context" property of global object.
105 private volatile Property contextProperty;
106
107 // default options passed to Nashorn Options object
108 private static final String[] DEFAULT_OPTIONS = new String[] { "-doe" };
109
110 // Nashorn script engine error message management
111 private static final String MESSAGES_RESOURCE = "jdk.nashorn.api.scripting.resources.Messages";
112
113 private static final ResourceBundle MESSAGES_BUNDLE;
114 static {
115 MESSAGES_BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault());
116 }
117
118 // helper to get Nashorn script engine error message
119 private static String getMessage(final String msgId, final String... args) {
120 try {
121 return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args);
122 } catch (final java.util.MissingResourceException e) {
247 public <T> T getInterface(final Object thiz, final Class<T> clazz) {
248 if (thiz == null) {
249 throw new IllegalArgumentException(getMessage("thiz.cannot.be.null"));
250 }
251 return getInterfaceInner(thiz, clazz);
252 }
253
254 // These are called from the "engine.js" script
255
256 /**
257 * This hook is used to search js global variables exposed from Java code.
258 *
259 * @param self 'this' passed from the script
260 * @param ctxt current ScriptContext in which name is searched
261 * @param name name of the variable searched
262 * @return the value of the named variable
263 */
264 public Object __noSuchProperty__(final Object self, final ScriptContext ctxt, final String name) {
265 if (ctxt != null) {
266 final int scope = ctxt.getAttributesScope(name);
267 final Global ctxtGlobal = getNashornGlobalFrom(ctxt);
268 if (scope != -1) {
269 return ScriptObjectMirror.unwrap(ctxt.getAttribute(name, scope), ctxtGlobal);
270 }
271
272 if (self == UNDEFINED) {
273 // scope access and so throw ReferenceError
274 throw referenceError(ctxtGlobal, "not.defined", name);
275 }
276 }
277
278 return UNDEFINED;
279 }
280
281 // Implementation only below this point
282
283 private static Source makeSource(final Reader reader, final ScriptContext ctxt) throws ScriptException {
284 try {
285 if (reader instanceof URLReader) {
286 final URL url = ((URLReader)reader).getURL();
287 final Charset cs = ((URLReader)reader).getCharset();
300 private static String getScriptName(final ScriptContext ctxt) {
301 final Object val = ctxt.getAttribute(ScriptEngine.FILENAME);
302 return (val != null) ? val.toString() : "<eval>";
303 }
304
305 private <T> T getInterfaceInner(final Object thiz, final Class<T> clazz) {
306 if (clazz == null || !clazz.isInterface()) {
307 throw new IllegalArgumentException(getMessage("interface.class.expected"));
308 }
309
310 // perform security access check as early as possible
311 final SecurityManager sm = System.getSecurityManager();
312 if (sm != null) {
313 if (! Modifier.isPublic(clazz.getModifiers())) {
314 throw new SecurityException(getMessage("implementing.non.public.interface", clazz.getName()));
315 }
316 Context.checkPackageAccess(clazz);
317 }
318
319 ScriptObject realSelf = null;
320 Global realGlobal = null;
321 if(thiz == null) {
322 // making interface out of global functions
323 realSelf = realGlobal = getNashornGlobalFrom(context);
324 } else if (thiz instanceof ScriptObjectMirror) {
325 final ScriptObjectMirror mirror = (ScriptObjectMirror)thiz;
326 realSelf = mirror.getScriptObject();
327 realGlobal = mirror.getHomeGlobal();
328 if (! isOfContext(realGlobal, nashornContext)) {
329 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
330 }
331 } else if (thiz instanceof ScriptObject) {
332 // called from script code.
333 realSelf = (ScriptObject)thiz;
334 realGlobal = Context.getGlobal();
335 if (realGlobal == null) {
336 throw new IllegalArgumentException(getMessage("no.current.nashorn.global"));
337 }
338
339 if (! isOfContext(realGlobal, nashornContext)) {
340 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
341 }
342 }
343
344 if (realSelf == null) {
345 throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
346 }
347
348 try {
349 final Global oldGlobal = Context.getGlobal();
350 final boolean globalChanged = (oldGlobal != realGlobal);
351 try {
352 if (globalChanged) {
353 Context.setGlobal(realGlobal);
354 }
355
356 if (! isInterfaceImplemented(clazz, realSelf)) {
357 return null;
358 }
359 return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz,
360 MethodHandles.publicLookup()).invoke(realSelf));
361 } finally {
362 if (globalChanged) {
363 Context.setGlobal(oldGlobal);
364 }
365 }
366 } catch(final RuntimeException|Error e) {
367 throw e;
368 } catch(final Throwable t) {
369 throw new RuntimeException(t);
370 }
371 }
372
373 // Retrieve nashorn Global object for a given ScriptContext object
374 private Global getNashornGlobalFrom(final ScriptContext ctxt) {
375 if (_global_per_engine) {
376 // shared single global object for all ENGINE_SCOPE Bindings
377 return global;
378 }
379
380 final Bindings bindings = ctxt.getBindings(ScriptContext.ENGINE_SCOPE);
381 // is this Nashorn's own Bindings implementation?
382 if (bindings instanceof ScriptObjectMirror) {
383 final Global glob = globalFromMirror((ScriptObjectMirror)bindings);
384 if (glob != null) {
385 return glob;
386 }
387 }
388
389 // Arbitrary user Bindings implementation. Look for NASHORN_GLOBAL in it!
390 Object scope = bindings.get(NASHORN_GLOBAL);
391 if (scope instanceof ScriptObjectMirror) {
392 final Global glob = globalFromMirror((ScriptObjectMirror)scope);
393 if (glob != null) {
394 return glob;
395 }
396 }
397
398 // We didn't find associated nashorn global mirror in the Bindings given!
399 // Create new global instance mirror and associate with the Bindings.
400 final ScriptObjectMirror mirror = createGlobalMirror(ctxt);
401 bindings.put(NASHORN_GLOBAL, mirror);
402 return mirror.getHomeGlobal();
403 }
404
405 // Retrieve nashorn Global object from a given ScriptObjectMirror
406 private Global globalFromMirror(final ScriptObjectMirror mirror) {
407 ScriptObject sobj = mirror.getScriptObject();
408 if (sobj instanceof Global && isOfContext((Global)sobj, nashornContext)) {
409 return (Global)sobj;
410 }
411
412 return null;
413 }
414
415 // Create a new ScriptObjectMirror wrapping a newly created Nashorn Global object
416 private ScriptObjectMirror createGlobalMirror(final ScriptContext ctxt) {
417 final Global newGlobal = createNashornGlobal(ctxt);
418 return new ScriptObjectMirror(newGlobal, newGlobal);
419 }
420
421 // Create a new Nashorn Global object
422 private Global createNashornGlobal(final ScriptContext ctxt) {
423 final Global newGlobal = AccessController.doPrivileged(new PrivilegedAction<Global>() {
424 @Override
425 public Global run() {
426 try {
427 return nashornContext.newGlobal();
428 } catch (final RuntimeException e) {
429 if (Context.DEBUG) {
430 e.printStackTrace();
431 }
432 throw e;
433 }
434 }
435 }, CREATE_GLOBAL_ACC_CTXT);
436
437 nashornContext.initGlobal(newGlobal);
438
439 final int NON_ENUMERABLE_CONSTANT = Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE | Property.NOT_WRITABLE;
440 // current ScriptContext exposed as "context"
441 // "context" is non-writable from script - but script engine still
442 // needs to set it and so save the context Property object
443 contextProperty = newGlobal.addOwnProperty("context", NON_ENUMERABLE_CONSTANT, ctxt);
444 // current ScriptEngine instance exposed as "engine". We added @SuppressWarnings("LeakingThisInConstructor") as
445 // NetBeans identifies this assignment as such a leak - this is a false positive as we're setting this property
446 // in the Global of a Context we just created - both the Context and the Global were just created and can not be
447 // seen from another thread outside of this constructor.
448 newGlobal.addOwnProperty("engine", NON_ENUMERABLE_CONSTANT, this);
449 // global script arguments with undefined value
450 newGlobal.addOwnProperty("arguments", Property.NOT_ENUMERABLE, UNDEFINED);
451 // file name default is null
452 newGlobal.addOwnProperty(ScriptEngine.FILENAME, Property.NOT_ENUMERABLE, null);
453 // evaluate engine.js initialization script this new global object
454 try {
455 evalImpl(compileImpl(ENGINE_SCRIPT_SRC, newGlobal), ctxt, newGlobal);
456 } catch (final ScriptException exp) {
457 throw new RuntimeException(exp);
458 }
459 return newGlobal;
460 }
461
462 // scripts should see "context" and "engine" as variables in the given global object
463 private void setContextVariables(final Global ctxtGlobal, final ScriptContext ctxt) {
464 // set "context" global variable via contextProperty - because this
465 // property is non-writable
466 contextProperty.setObjectValue(ctxtGlobal, ctxtGlobal, ctxt, false);
467 Object args = ScriptObjectMirror.unwrap(ctxt.getAttribute("arguments"), ctxtGlobal);
468 if (args == null || args == UNDEFINED) {
469 args = ScriptRuntime.EMPTY_ARRAY;
470 }
471 // if no arguments passed, expose it
472 if (! (args instanceof ScriptObject)) {
473 args = ctxtGlobal.wrapAsObject(args);
474 ctxtGlobal.set("arguments", args, false);
475 }
476 }
477
478 private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
479 name.getClass(); // null check
480
481 Global invokeGlobal = null;
482 ScriptObjectMirror selfMirror = null;
483 if (selfObject instanceof ScriptObjectMirror) {
484 selfMirror = (ScriptObjectMirror)selfObject;
485 if (! isOfContext(selfMirror.getHomeGlobal(), nashornContext)) {
486 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
487 }
488 invokeGlobal = selfMirror.getHomeGlobal();
489 } else if (selfObject instanceof ScriptObject) {
490 // invokeMethod called from script code - in which case we may get 'naked' ScriptObject
491 // Wrap it with oldGlobal to make a ScriptObjectMirror for the same.
492 final Global oldGlobal = Context.getGlobal();
493 invokeGlobal = oldGlobal;
494 if (oldGlobal == null) {
495 throw new IllegalArgumentException(getMessage("no.current.nashorn.global"));
496 }
497
498 if (! isOfContext(oldGlobal, nashornContext)) {
499 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
500 }
501
502 selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(selfObject, oldGlobal);
503 } else if (selfObject == null) {
504 // selfObject is null => global function call
505 final Global ctxtGlobal = getNashornGlobalFrom(context);
506 invokeGlobal = ctxtGlobal;
507 selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(ctxtGlobal, ctxtGlobal);
508 }
509
510 if (selfMirror != null) {
511 try {
512 return ScriptObjectMirror.translateUndefined(selfMirror.callMember(name, args));
513 } catch (final Exception e) {
514 final Throwable cause = e.getCause();
515 if (cause instanceof NoSuchMethodException) {
516 throw (NoSuchMethodException)cause;
517 }
518 throwAsScriptException(e, invokeGlobal);
519 throw new AssertionError("should not reach here");
520 }
521 }
522
523 // Non-script object passed as selfObject
524 throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
525 }
526
527 private Object evalImpl(final Source src, final ScriptContext ctxt) throws ScriptException {
528 return evalImpl(compileImpl(src, ctxt), ctxt);
529 }
530
531 private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException {
532 return evalImpl(script, ctxt, getNashornGlobalFrom(ctxt));
533 }
534
535 private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException {
536 if (script == null) {
537 return null;
538 }
539 final Global oldGlobal = Context.getGlobal();
540 final boolean globalChanged = (oldGlobal != ctxtGlobal);
541 try {
542 if (globalChanged) {
543 Context.setGlobal(ctxtGlobal);
544 }
545
546 // set ScriptContext variables if ctxt is non-null
547 if (ctxt != null) {
548 setContextVariables(ctxtGlobal, ctxt);
549 }
550 return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal));
551 } catch (final Exception e) {
552 throwAsScriptException(e, ctxtGlobal);
553 throw new AssertionError("should not reach here");
554 } finally {
555 if (globalChanged) {
556 Context.setGlobal(oldGlobal);
557 }
558 }
559 }
560
561 private static void throwAsScriptException(final Exception e, final Global global) throws ScriptException {
562 if (e instanceof ScriptException) {
563 throw (ScriptException)e;
564 } else if (e instanceof NashornException) {
565 final NashornException ne = (NashornException)e;
566 final ScriptException se = new ScriptException(
567 ne.getMessage(), ne.getFileName(),
568 ne.getLineNumber(), ne.getColumnNumber());
569 ne.initEcmaError(global);
570 se.initCause(e);
571 throw se;
572 } else if (e instanceof RuntimeException) {
573 throw (RuntimeException)e;
574 } else {
575 // wrap any other exception as ScriptException
576 throw new ScriptException(e);
577 }
578 }
579
580 private CompiledScript asCompiledScript(final Source source) throws ScriptException {
581 final ScriptFunction func = compileImpl(source, context);
582 return new CompiledScript() {
583 @Override
584 public Object eval(final ScriptContext ctxt) throws ScriptException {
585 final Global globalObject = getNashornGlobalFrom(ctxt);
586 // Are we running the script in the correct global?
587 if (func.getScope() == globalObject) {
588 return evalImpl(func, ctxt, globalObject);
589 }
590 // ScriptContext with a different global. Compile again!
591 // Note that we may still hit per-global compilation cache.
592 return evalImpl(compileImpl(source, ctxt), ctxt, globalObject);
593 }
594 @Override
595 public ScriptEngine getEngine() {
596 return NashornScriptEngine.this;
597 }
598 };
599 }
600
601 private ScriptFunction compileImpl(final Source source, final ScriptContext ctxt) throws ScriptException {
602 return compileImpl(source, getNashornGlobalFrom(ctxt));
603 }
604
605 private ScriptFunction compileImpl(final Source source, final Global newGlobal) throws ScriptException {
606 final Global oldGlobal = Context.getGlobal();
607 final boolean globalChanged = (oldGlobal != newGlobal);
608 try {
609 if (globalChanged) {
610 Context.setGlobal(newGlobal);
611 }
612
613 return nashornContext.compileScript(source, newGlobal);
614 } catch (final Exception e) {
615 throwAsScriptException(e, newGlobal);
616 throw new AssertionError("should not reach here");
617 } finally {
618 if (globalChanged) {
619 Context.setGlobal(oldGlobal);
620 }
621 }
622 }
623
624 private static boolean isInterfaceImplemented(final Class<?> iface, final ScriptObject sobj) {
625 for (final Method method : iface.getMethods()) {
626 // ignore methods of java.lang.Object class
627 if (method.getDeclaringClass() == Object.class) {
628 continue;
629 }
630
631 // skip check for default methods - non-abstract, interface methods
632 if (! Modifier.isAbstract(method.getModifiers())) {
633 continue;
634 }
635
636 Object obj = sobj.get(method.getName());
637 if (! (obj instanceof ScriptFunction)) {
638 return false;
639 }
640 }
641 return true;
642 }
643
644 private static boolean isOfContext(final Global global, final Context context) {
645 return global.isOfContext(context);
646 }
647 }
|