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