NashornScriptEngine.java revision 1590:1916a2c680d8
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 26package jdk.nashorn.api.scripting; 27 28import static jdk.nashorn.internal.runtime.Source.sourceFor; 29 30import java.io.IOException; 31import java.io.Reader; 32import java.lang.invoke.MethodHandles; 33import java.lang.reflect.Method; 34import java.lang.reflect.Modifier; 35import java.security.AccessControlContext; 36import java.security.AccessController; 37import java.security.Permissions; 38import java.security.PrivilegedAction; 39import java.security.ProtectionDomain; 40import java.text.MessageFormat; 41import java.util.Locale; 42import java.util.Objects; 43import java.util.ResourceBundle; 44import javax.script.AbstractScriptEngine; 45import javax.script.Bindings; 46import javax.script.Compilable; 47import javax.script.CompiledScript; 48import javax.script.Invocable; 49import javax.script.ScriptContext; 50import javax.script.ScriptEngine; 51import javax.script.ScriptEngineFactory; 52import javax.script.ScriptException; 53import javax.script.SimpleBindings; 54import jdk.nashorn.internal.objects.Global; 55import jdk.nashorn.internal.runtime.Context; 56import jdk.nashorn.internal.runtime.ErrorManager; 57import jdk.nashorn.internal.runtime.ScriptFunction; 58import jdk.nashorn.internal.runtime.ScriptObject; 59import jdk.nashorn.internal.runtime.ScriptRuntime; 60import jdk.nashorn.internal.runtime.Source; 61import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory; 62import 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 * @since 1.8u40 71 */ 72public 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(); 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(); 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(); 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() { 336 final Global newGlobal = createNashornGlobal(); 337 return new ScriptObjectMirror(newGlobal, newGlobal); 338 } 339 340 // Create a new Nashorn Global object 341 private Global createNashornGlobal() { 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 358 return newGlobal; 359 } 360 361 private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException { 362 Objects.requireNonNull(name); 363 assert !(selfObject instanceof ScriptObject) : "raw ScriptObject not expected here"; 364 365 Global invokeGlobal = null; 366 ScriptObjectMirror selfMirror = null; 367 if (selfObject instanceof ScriptObjectMirror) { 368 selfMirror = (ScriptObjectMirror)selfObject; 369 if (! isOfContext(selfMirror.getHomeGlobal(), nashornContext)) { 370 throw new IllegalArgumentException(getMessage("script.object.from.another.engine")); 371 } 372 invokeGlobal = selfMirror.getHomeGlobal(); 373 } else if (selfObject == null) { 374 // selfObject is null => global function call 375 final Global ctxtGlobal = getNashornGlobalFrom(context); 376 invokeGlobal = ctxtGlobal; 377 selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(ctxtGlobal, ctxtGlobal); 378 } 379 380 if (selfMirror != null) { 381 try { 382 return ScriptObjectMirror.translateUndefined(selfMirror.callMember(name, args)); 383 } catch (final Exception e) { 384 final Throwable cause = e.getCause(); 385 if (cause instanceof NoSuchMethodException) { 386 throw (NoSuchMethodException)cause; 387 } 388 throwAsScriptException(e, invokeGlobal); 389 throw new AssertionError("should not reach here"); 390 } 391 } 392 393 // Non-script object passed as selfObject 394 throw new IllegalArgumentException(getMessage("interface.on.non.script.object")); 395 } 396 397 private Object evalImpl(final Source src, final ScriptContext ctxt) throws ScriptException { 398 return evalImpl(compileImpl(src, ctxt), ctxt); 399 } 400 401 private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException { 402 return evalImpl(script, ctxt, getNashornGlobalFrom(ctxt)); 403 } 404 405 private Object evalImpl(final Context.MultiGlobalCompiledScript mgcs, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { 406 final Global oldGlobal = Context.getGlobal(); 407 final boolean globalChanged = (oldGlobal != ctxtGlobal); 408 try { 409 if (globalChanged) { 410 Context.setGlobal(ctxtGlobal); 411 } 412 413 final ScriptFunction script = mgcs.getFunction(ctxtGlobal); 414 final ScriptContext oldCtxt = ctxtGlobal.getScriptContext(); 415 ctxtGlobal.setScriptContext(ctxt); 416 try { 417 return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal)); 418 } finally { 419 ctxtGlobal.setScriptContext(oldCtxt); 420 } 421 } catch (final Exception e) { 422 throwAsScriptException(e, ctxtGlobal); 423 throw new AssertionError("should not reach here"); 424 } finally { 425 if (globalChanged) { 426 Context.setGlobal(oldGlobal); 427 } 428 } 429 } 430 431 private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { 432 if (script == null) { 433 return null; 434 } 435 final Global oldGlobal = Context.getGlobal(); 436 final boolean globalChanged = (oldGlobal != ctxtGlobal); 437 try { 438 if (globalChanged) { 439 Context.setGlobal(ctxtGlobal); 440 } 441 442 final ScriptContext oldCtxt = ctxtGlobal.getScriptContext(); 443 ctxtGlobal.setScriptContext(ctxt); 444 try { 445 return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal)); 446 } finally { 447 ctxtGlobal.setScriptContext(oldCtxt); 448 } 449 } catch (final Exception e) { 450 throwAsScriptException(e, ctxtGlobal); 451 throw new AssertionError("should not reach here"); 452 } finally { 453 if (globalChanged) { 454 Context.setGlobal(oldGlobal); 455 } 456 } 457 } 458 459 private static void throwAsScriptException(final Exception e, final Global global) throws ScriptException { 460 if (e instanceof ScriptException) { 461 throw (ScriptException)e; 462 } else if (e instanceof NashornException) { 463 final NashornException ne = (NashornException)e; 464 final ScriptException se = new ScriptException( 465 ne.getMessage(), ne.getFileName(), 466 ne.getLineNumber(), ne.getColumnNumber()); 467 ne.initEcmaError(global); 468 se.initCause(e); 469 throw se; 470 } else if (e instanceof RuntimeException) { 471 throw (RuntimeException)e; 472 } else { 473 // wrap any other exception as ScriptException 474 throw new ScriptException(e); 475 } 476 } 477 478 private CompiledScript asCompiledScript(final Source source) throws ScriptException { 479 final Context.MultiGlobalCompiledScript mgcs; 480 final ScriptFunction func; 481 final Global oldGlobal = Context.getGlobal(); 482 final Global newGlobal = getNashornGlobalFrom(context); 483 final boolean globalChanged = (oldGlobal != newGlobal); 484 try { 485 if (globalChanged) { 486 Context.setGlobal(newGlobal); 487 } 488 489 mgcs = nashornContext.compileScript(source); 490 func = mgcs.getFunction(newGlobal); 491 } catch (final Exception e) { 492 throwAsScriptException(e, newGlobal); 493 throw new AssertionError("should not reach here"); 494 } finally { 495 if (globalChanged) { 496 Context.setGlobal(oldGlobal); 497 } 498 } 499 500 return new CompiledScript() { 501 @Override 502 public Object eval(final ScriptContext ctxt) throws ScriptException { 503 final Global globalObject = getNashornGlobalFrom(ctxt); 504 // Are we running the script in the same global in which it was compiled? 505 if (func.getScope() == globalObject) { 506 return evalImpl(func, ctxt, globalObject); 507 } 508 509 // different global 510 return evalImpl(mgcs, ctxt, globalObject); 511 } 512 @Override 513 public ScriptEngine getEngine() { 514 return NashornScriptEngine.this; 515 } 516 }; 517 } 518 519 private ScriptFunction compileImpl(final Source source, final ScriptContext ctxt) throws ScriptException { 520 return compileImpl(source, getNashornGlobalFrom(ctxt)); 521 } 522 523 private ScriptFunction compileImpl(final Source source, final Global newGlobal) throws ScriptException { 524 final Global oldGlobal = Context.getGlobal(); 525 final boolean globalChanged = (oldGlobal != newGlobal); 526 try { 527 if (globalChanged) { 528 Context.setGlobal(newGlobal); 529 } 530 531 return nashornContext.compileScript(source, newGlobal); 532 } catch (final Exception e) { 533 throwAsScriptException(e, newGlobal); 534 throw new AssertionError("should not reach here"); 535 } finally { 536 if (globalChanged) { 537 Context.setGlobal(oldGlobal); 538 } 539 } 540 } 541 542 private static boolean isInterfaceImplemented(final Class<?> iface, final ScriptObject sobj) { 543 for (final Method method : iface.getMethods()) { 544 // ignore methods of java.lang.Object class 545 if (method.getDeclaringClass() == Object.class) { 546 continue; 547 } 548 549 // skip check for default methods - non-abstract, interface methods 550 if (! Modifier.isAbstract(method.getModifiers())) { 551 continue; 552 } 553 554 final Object obj = sobj.get(method.getName()); 555 if (! (obj instanceof ScriptFunction)) { 556 return false; 557 } 558 } 559 return true; 560 } 561 562 private static boolean isOfContext(final Global global, final Context context) { 563 return global.isOfContext(context); 564 } 565} 566