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