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
  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
 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 
 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
 156     public Object eval(final Reader reader, final ScriptContext ctxt) throws ScriptException {
 157         return evalImpl(makeSource(reader, ctxt), ctxt);
 158     }
 159 
 160     @Override
 161     public Object eval(final String script, final ScriptContext ctxt) throws ScriptException {
 162         return evalImpl(makeSource(script, ctxt), ctxt);
 163     }
 164 
 165     @Override
 166     public ScriptEngineFactory getFactory() {
 167         return factory;
 168     }
 169 
 170     @Override
 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
 183     public CompiledScript compile(final Reader reader) throws ScriptException {
 184         return asCompiledScript(makeSource(reader, context));
 185     }
 186 
 187     @Override
 188     public CompiledScript compile(final String str) throws ScriptException {
 189         return asCompiledScript(makeSource(str, context));
 190     }
 191 
 192     // Invocable methods
 193 
 194     @Override
 195     public Object invokeFunction(final String name, final Object... args)
 196             throws ScriptException, NoSuchMethodException {
 197         return invokeImpl(null, name, args);
 198     }
 199 
 200     @Override
 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
 210     public <T> T getInterface(final Class<T> clazz) {
 211         return getInterfaceInner(null, clazz);
 212     }
 213 
 214     @Override
 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 
 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 
 232     private static Source makeSource(final String src, final ScriptContext ctxt) {
 233         return sourceFor(getScriptName(ctxt), src);
 234     }
 235 
 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 
 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
 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
 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
 346     private ScriptObjectMirror createGlobalMirror() {
 347         final Global newGlobal = createNashornGlobal();
 348         return new ScriptObjectMirror(newGlobal, newGlobal);
 349     }
 350 
 351     // Create a new Nashorn Global object
 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 
 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 
 408     private Object evalImpl(final Source src, final ScriptContext ctxt) throws ScriptException {
 409         return evalImpl(compileImpl(src, ctxt), ctxt);
 410     }
 411 
 412     private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException {
 413         return evalImpl(script, ctxt, getNashornGlobalFrom(ctxt));
 414     }
 415 
 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 
 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 
 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 }