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.Objects; 43 import java.util.ResourceBundle; 44 import javax.script.AbstractScriptEngine; 45 import javax.script.Bindings; 46 import javax.script.Compilable; 47 import javax.script.CompiledScript; 48 import javax.script.Invocable; 49 import javax.script.ScriptContext; 50 import javax.script.ScriptEngine; 51 import javax.script.ScriptEngineFactory; 52 import javax.script.ScriptException; 53 import javax.script.SimpleBindings; 54 import jdk.nashorn.internal.objects.Global; 55 import jdk.nashorn.internal.runtime.Context; 56 import jdk.nashorn.internal.runtime.ErrorManager; 57 import jdk.nashorn.internal.runtime.ScriptFunction; 58 import jdk.nashorn.internal.runtime.ScriptObject; 59 import jdk.nashorn.internal.runtime.ScriptRuntime; 60 import jdk.nashorn.internal.runtime.Source; 61 import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory; 62 import 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 * @deprecated Nashorn JavaScript script engine and APIs, and the jjs tool 71 * are deprecated with the intent to remove them in a future release. 72 * 73 * @since 1.8u40 74 */ 75 @Deprecated(since="11", forRemoval=true) 76 public final class NashornScriptEngine extends AbstractScriptEngine implements Compilable, Invocable { 77 /** 78 * Key used to associate Nashorn global object mirror with arbitrary Bindings instance. 79 */ 80 public static final String NASHORN_GLOBAL = "nashorn.global"; 81 82 // commonly used access control context objects createPermAccCtxt(final String permName)83 private static AccessControlContext createPermAccCtxt(final String permName) { 84 final Permissions perms = new Permissions(); 85 perms.add(new RuntimePermission(permName)); 86 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) }); 87 } 88 89 private static final AccessControlContext CREATE_CONTEXT_ACC_CTXT = createPermAccCtxt(Context.NASHORN_CREATE_CONTEXT); 90 private static final AccessControlContext CREATE_GLOBAL_ACC_CTXT = createPermAccCtxt(Context.NASHORN_CREATE_GLOBAL); 91 92 // the factory that created this engine 93 private final ScriptEngineFactory factory; 94 // underlying nashorn Context - 1:1 with engine instance 95 private final Context nashornContext; 96 // do we want to share single Nashorn global instance across ENGINE_SCOPEs? 97 private final boolean _global_per_engine; 98 // This is the initial default Nashorn global object. 99 // This is used as "shared" global if above option is true. 100 private final Global global; 101 102 // Nashorn script engine error message management 103 private static final String MESSAGES_RESOURCE = "jdk.nashorn.api.scripting.resources.Messages"; 104 105 private static final ResourceBundle MESSAGES_BUNDLE; 106 static { 107 MESSAGES_BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault()); 108 } 109 110 // helper to get Nashorn script engine error message getMessage(final String msgId, final String... args)111 private static String getMessage(final String msgId, final String... args) { 112 try { 113 return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args); 114 } catch (final java.util.MissingResourceException e) { 115 throw new RuntimeException("no message resource found for message id: "+ msgId); 116 } 117 } 118 NashornScriptEngine(final NashornScriptEngineFactory factory, final String[] args, final ClassLoader appLoader, final ClassFilter classFilter)119 NashornScriptEngine(final NashornScriptEngineFactory factory, final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) { 120 assert args != null : "null argument array"; 121 this.factory = factory; 122 final Options options = new Options("nashorn"); 123 options.process(args); 124 125 // throw ParseException on first error from script 126 final ErrorManager errMgr = new Context.ThrowErrorManager(); 127 // create new Nashorn Context 128 this.nashornContext = AccessController.doPrivileged(new PrivilegedAction<Context>() { 129 @Override 130 public Context run() { 131 try { 132 return new Context(options, errMgr, appLoader, classFilter); 133 } catch (final RuntimeException e) { 134 if (Context.DEBUG) { 135 e.printStackTrace(); 136 } 137 throw e; 138 } 139 } 140 }, CREATE_CONTEXT_ACC_CTXT); 141 142 if (!nashornContext.getEnv()._no_deprecation_warning) { 143 System.err.println("Warning: Nashorn engine is planned to be removed from a future JDK release"); 144 } 145 146 // cache this option that is used often 147 this._global_per_engine = nashornContext.getEnv()._global_per_engine; 148 149 // create new global object 150 this.global = createNashornGlobal(); 151 // set the default ENGINE_SCOPE object for the default context 152 context.setBindings(new ScriptObjectMirror(global, global), ScriptContext.ENGINE_SCOPE); 153 } 154 155 @Override eval(final Reader reader, final ScriptContext ctxt)156 public Object eval(final Reader reader, final ScriptContext ctxt) throws ScriptException { 157 return evalImpl(makeSource(reader, ctxt), ctxt); 158 } 159 160 @Override eval(final String script, final ScriptContext ctxt)161 public Object eval(final String script, final ScriptContext ctxt) throws ScriptException { 162 return evalImpl(makeSource(script, ctxt), ctxt); 163 } 164 165 @Override getFactory()166 public ScriptEngineFactory getFactory() { 167 return factory; 168 } 169 170 @Override createBindings()171 public Bindings createBindings() { 172 if (_global_per_engine) { 173 // just create normal SimpleBindings. 174 // We use same 'global' for all Bindings. 175 return new SimpleBindings(); 176 } 177 return createGlobalMirror(); 178 } 179 180 // Compilable methods 181 182 @Override compile(final Reader reader)183 public CompiledScript compile(final Reader reader) throws ScriptException { 184 return asCompiledScript(makeSource(reader, context)); 185 } 186 187 @Override compile(final String str)188 public CompiledScript compile(final String str) throws ScriptException { 189 return asCompiledScript(makeSource(str, context)); 190 } 191 192 // Invocable methods 193 194 @Override invokeFunction(final String name, final Object... args)195 public Object invokeFunction(final String name, final Object... args) 196 throws ScriptException, NoSuchMethodException { 197 return invokeImpl(null, name, args); 198 } 199 200 @Override invokeMethod(final Object thiz, final String name, final Object... args)201 public Object invokeMethod(final Object thiz, final String name, final Object... args) 202 throws ScriptException, NoSuchMethodException { 203 if (thiz == null) { 204 throw new IllegalArgumentException(getMessage("thiz.cannot.be.null")); 205 } 206 return invokeImpl(thiz, name, args); 207 } 208 209 @Override getInterface(final Class<T> clazz)210 public <T> T getInterface(final Class<T> clazz) { 211 return getInterfaceInner(null, clazz); 212 } 213 214 @Override getInterface(final Object thiz, final Class<T> clazz)215 public <T> T getInterface(final Object thiz, final Class<T> clazz) { 216 if (thiz == null) { 217 throw new IllegalArgumentException(getMessage("thiz.cannot.be.null")); 218 } 219 return getInterfaceInner(thiz, clazz); 220 } 221 222 // Implementation only below this point 223 makeSource(final Reader reader, final ScriptContext ctxt)224 private static Source makeSource(final Reader reader, final ScriptContext ctxt) throws ScriptException { 225 try { 226 return sourceFor(getScriptName(ctxt), reader); 227 } catch (final IOException e) { 228 throw new ScriptException(e); 229 } 230 } 231 makeSource(final String src, final ScriptContext ctxt)232 private static Source makeSource(final String src, final ScriptContext ctxt) { 233 return sourceFor(getScriptName(ctxt), src); 234 } 235 getScriptName(final ScriptContext ctxt)236 private static String getScriptName(final ScriptContext ctxt) { 237 final Object val = ctxt.getAttribute(ScriptEngine.FILENAME); 238 return (val != null) ? val.toString() : "<eval>"; 239 } 240 getInterfaceInner(final Object thiz, final Class<T> clazz)241 private <T> T getInterfaceInner(final Object thiz, final Class<T> clazz) { 242 assert !(thiz instanceof ScriptObject) : "raw ScriptObject not expected here"; 243 244 if (clazz == null || !clazz.isInterface()) { 245 throw new IllegalArgumentException(getMessage("interface.class.expected")); 246 } 247 248 // perform security access check as early as possible 249 final SecurityManager sm = System.getSecurityManager(); 250 if (sm != null) { 251 if (! Modifier.isPublic(clazz.getModifiers())) { 252 throw new SecurityException(getMessage("implementing.non.public.interface", clazz.getName())); 253 } 254 Context.checkPackageAccess(clazz); 255 } 256 257 ScriptObject realSelf = null; 258 Global realGlobal = null; 259 if(thiz == null) { 260 // making interface out of global functions 261 realSelf = realGlobal = getNashornGlobalFrom(context); 262 } else if (thiz instanceof ScriptObjectMirror) { 263 final ScriptObjectMirror mirror = (ScriptObjectMirror)thiz; 264 realSelf = mirror.getScriptObject(); 265 realGlobal = mirror.getHomeGlobal(); 266 if (! isOfContext(realGlobal, nashornContext)) { 267 throw new IllegalArgumentException(getMessage("script.object.from.another.engine")); 268 } 269 } 270 271 if (realSelf == null) { 272 throw new IllegalArgumentException(getMessage("interface.on.non.script.object")); 273 } 274 275 try { 276 final Global oldGlobal = Context.getGlobal(); 277 final boolean globalChanged = (oldGlobal != realGlobal); 278 try { 279 if (globalChanged) { 280 Context.setGlobal(realGlobal); 281 } 282 283 if (! isInterfaceImplemented(clazz, realSelf)) { 284 return null; 285 } 286 return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz, 287 MethodHandles.publicLookup()).invoke(realSelf)); 288 } finally { 289 if (globalChanged) { 290 Context.setGlobal(oldGlobal); 291 } 292 } 293 } catch(final RuntimeException|Error e) { 294 throw e; 295 } catch(final Throwable t) { 296 throw new RuntimeException(t); 297 } 298 } 299 300 // Retrieve nashorn Global object for a given ScriptContext object getNashornGlobalFrom(final ScriptContext ctxt)301 private Global getNashornGlobalFrom(final ScriptContext ctxt) { 302 if (_global_per_engine) { 303 // shared single global object for all ENGINE_SCOPE Bindings 304 return global; 305 } 306 307 final Bindings bindings = ctxt.getBindings(ScriptContext.ENGINE_SCOPE); 308 // is this Nashorn's own Bindings implementation? 309 if (bindings instanceof ScriptObjectMirror) { 310 final Global glob = globalFromMirror((ScriptObjectMirror)bindings); 311 if (glob != null) { 312 return glob; 313 } 314 } 315 316 // Arbitrary user Bindings implementation. Look for NASHORN_GLOBAL in it! 317 final Object scope = bindings.get(NASHORN_GLOBAL); 318 if (scope instanceof ScriptObjectMirror) { 319 final Global glob = globalFromMirror((ScriptObjectMirror)scope); 320 if (glob != null) { 321 return glob; 322 } 323 } 324 325 // We didn't find associated nashorn global mirror in the Bindings given! 326 // Create new global instance mirror and associate with the Bindings. 327 final ScriptObjectMirror mirror = createGlobalMirror(); 328 bindings.put(NASHORN_GLOBAL, mirror); 329 // Since we created this global explicitly for the non-default script context we set the 330 // current script context in global permanently so that invokes work as expected. See JDK-8150219 331 mirror.getHomeGlobal().setInitScriptContext(ctxt); 332 return mirror.getHomeGlobal(); 333 } 334 335 // Retrieve nashorn Global object from a given ScriptObjectMirror globalFromMirror(final ScriptObjectMirror mirror)336 private Global globalFromMirror(final ScriptObjectMirror mirror) { 337 final ScriptObject sobj = mirror.getScriptObject(); 338 if (sobj instanceof Global && isOfContext((Global)sobj, nashornContext)) { 339 return (Global)sobj; 340 } 341 342 return null; 343 } 344 345 // Create a new ScriptObjectMirror wrapping a newly created Nashorn Global object createGlobalMirror()346 private ScriptObjectMirror createGlobalMirror() { 347 final Global newGlobal = createNashornGlobal(); 348 return new ScriptObjectMirror(newGlobal, newGlobal); 349 } 350 351 // Create a new Nashorn Global object createNashornGlobal()352 private Global createNashornGlobal() { 353 final Global newGlobal = AccessController.doPrivileged(new PrivilegedAction<Global>() { 354 @Override 355 public Global run() { 356 try { 357 return nashornContext.newGlobal(); 358 } catch (final RuntimeException e) { 359 if (Context.DEBUG) { 360 e.printStackTrace(); 361 } 362 throw e; 363 } 364 } 365 }, CREATE_GLOBAL_ACC_CTXT); 366 367 nashornContext.initGlobal(newGlobal, this); 368 369 return newGlobal; 370 } 371 invokeImpl(final Object selfObject, final String name, final Object... args)372 private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException { 373 Objects.requireNonNull(name); 374 assert !(selfObject instanceof ScriptObject) : "raw ScriptObject not expected here"; 375 376 Global invokeGlobal = null; 377 ScriptObjectMirror selfMirror = null; 378 if (selfObject instanceof ScriptObjectMirror) { 379 selfMirror = (ScriptObjectMirror)selfObject; 380 if (! isOfContext(selfMirror.getHomeGlobal(), nashornContext)) { 381 throw new IllegalArgumentException(getMessage("script.object.from.another.engine")); 382 } 383 invokeGlobal = selfMirror.getHomeGlobal(); 384 } else if (selfObject == null) { 385 // selfObject is null => global function call 386 final Global ctxtGlobal = getNashornGlobalFrom(context); 387 invokeGlobal = ctxtGlobal; 388 selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(ctxtGlobal, ctxtGlobal); 389 } 390 391 if (selfMirror != null) { 392 try { 393 return ScriptObjectMirror.translateUndefined(selfMirror.callMember(name, args)); 394 } catch (final Exception e) { 395 final Throwable cause = e.getCause(); 396 if (cause instanceof NoSuchMethodException) { 397 throw (NoSuchMethodException)cause; 398 } 399 throwAsScriptException(e, invokeGlobal); 400 throw new AssertionError("should not reach here"); 401 } 402 } 403 404 // Non-script object passed as selfObject 405 throw new IllegalArgumentException(getMessage("interface.on.non.script.object")); 406 } 407 evalImpl(final Source src, final ScriptContext ctxt)408 private Object evalImpl(final Source src, final ScriptContext ctxt) throws ScriptException { 409 return evalImpl(compileImpl(src, ctxt), ctxt); 410 } 411 evalImpl(final ScriptFunction script, final ScriptContext ctxt)412 private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException { 413 return evalImpl(script, ctxt, getNashornGlobalFrom(ctxt)); 414 } 415 evalImpl(final Context.MultiGlobalCompiledScript mgcs, final ScriptContext ctxt, final Global ctxtGlobal)416 private Object evalImpl(final Context.MultiGlobalCompiledScript mgcs, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { 417 final Global oldGlobal = Context.getGlobal(); 418 final boolean globalChanged = (oldGlobal != ctxtGlobal); 419 try { 420 if (globalChanged) { 421 Context.setGlobal(ctxtGlobal); 422 } 423 424 final ScriptFunction script = mgcs.getFunction(ctxtGlobal); 425 final ScriptContext oldCtxt = ctxtGlobal.getScriptContext(); 426 ctxtGlobal.setScriptContext(ctxt); 427 try { 428 return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal)); 429 } finally { 430 ctxtGlobal.setScriptContext(oldCtxt); 431 } 432 } catch (final Exception e) { 433 throwAsScriptException(e, ctxtGlobal); 434 throw new AssertionError("should not reach here"); 435 } finally { 436 if (globalChanged) { 437 Context.setGlobal(oldGlobal); 438 } 439 } 440 } 441 evalImpl(final ScriptFunction script, final ScriptContext ctxt, final Global ctxtGlobal)442 private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { 443 if (script == null) { 444 return null; 445 } 446 final Global oldGlobal = Context.getGlobal(); 447 final boolean globalChanged = (oldGlobal != ctxtGlobal); 448 try { 449 if (globalChanged) { 450 Context.setGlobal(ctxtGlobal); 451 } 452 453 final ScriptContext oldCtxt = ctxtGlobal.getScriptContext(); 454 ctxtGlobal.setScriptContext(ctxt); 455 try { 456 return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal)); 457 } finally { 458 ctxtGlobal.setScriptContext(oldCtxt); 459 } 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 throwAsScriptException(final Exception e, final Global global)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 asCompiledScript(final Source source)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