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