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.ECMAErrors.referenceError;
  29 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
  30 
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.io.InputStreamReader;
  34 import java.io.Reader;
  35 import java.lang.reflect.Method;
  36 import java.lang.reflect.Modifier;
  37 import java.net.URL;
  38 import java.nio.charset.Charset;
  39 import java.security.AccessControlContext;
  40 import java.security.AccessController;
  41 import java.security.Permissions;
  42 import java.security.PrivilegedAction;
  43 import java.security.PrivilegedActionException;
  44 import java.security.PrivilegedExceptionAction;
  45 import java.security.ProtectionDomain;
  46 import java.text.MessageFormat;
  47 import java.util.Locale;
  48 import java.util.ResourceBundle;
  49 import javax.script.AbstractScriptEngine;
  50 import javax.script.Bindings;
  51 import javax.script.Compilable;
  52 import javax.script.CompiledScript;
  53 import javax.script.Invocable;
  54 import javax.script.ScriptContext;
  55 import javax.script.ScriptEngine;
  56 import javax.script.ScriptEngineFactory;
  57 import javax.script.ScriptException;
  58 import javax.script.SimpleBindings;
  59 import jdk.nashorn.internal.runtime.Context;
  60 import jdk.nashorn.internal.runtime.ErrorManager;
  61 import jdk.nashorn.internal.runtime.GlobalObject;
  62 import jdk.nashorn.internal.runtime.Property;
  63 import jdk.nashorn.internal.runtime.ScriptFunction;
  64 import jdk.nashorn.internal.runtime.ScriptObject;
  65 import jdk.nashorn.internal.runtime.ScriptRuntime;
  66 import jdk.nashorn.internal.runtime.Source;
  67 import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
  68 import jdk.nashorn.internal.runtime.options.Options;
  69 
  70 /**
  71  * JSR-223 compliant script engine for Nashorn. Instances are not created directly, but rather returned through
  72  * {@link NashornScriptEngineFactory#getScriptEngine()}. Note that this engine implements the {@link Compilable} and
  73  * {@link Invocable} interfaces, allowing for efficient precompilation and repeated execution of scripts.
  74  * @see NashornScriptEngineFactory
  75  */
  76 
  77 public final class NashornScriptEngine extends AbstractScriptEngine implements Compilable, Invocable {
  78     /**
  79      * Key used to associate Nashorn global object mirror with arbitrary Bindings instance.
  80      */
  81     public static final String NASHORN_GLOBAL = "nashorn.global";
  82 
  83     // commonly used access control context objects
  84     private static AccessControlContext createPermAccCtxt(final String permName) {
  85         final Permissions perms = new Permissions();
  86         perms.add(new RuntimePermission(permName));
  87         return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
  88     }
  89 
  90     private static final AccessControlContext CREATE_CONTEXT_ACC_CTXT = createPermAccCtxt(Context.NASHORN_CREATE_CONTEXT);
  91     private static final AccessControlContext CREATE_GLOBAL_ACC_CTXT  = createPermAccCtxt(Context.NASHORN_CREATE_GLOBAL);
  92 
  93     // the factory that created this engine
  94     private final ScriptEngineFactory factory;
  95     // underlying nashorn Context - 1:1 with engine instance
  96     private final Context             nashornContext;
  97     // do we want to share single Nashorn global instance across ENGINE_SCOPEs?
  98     private final boolean             _global_per_engine;
  99     // This is the initial default Nashorn global object.
 100     // This is used as "shared" global if above option is true.
 101     private final ScriptObject        global;
 102     // initialized bit late to be made 'final'.
 103     // Property object for "context" property of global object.
 104     private volatile Property         contextProperty;
 105 
 106     // default options passed to Nashorn Options object
 107     private static final String[] DEFAULT_OPTIONS = new String[] { "-scripting", "-doe" };
 108 
 109     // Nashorn script engine error message management
 110     private static final String MESSAGES_RESOURCE = "jdk.nashorn.api.scripting.resources.Messages";
 111 
 112     private static final ResourceBundle MESSAGES_BUNDLE;
 113     static {
 114         MESSAGES_BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault());
 115     }
 116 
 117     // helper to get Nashorn script engine error message
 118     private static String getMessage(final String msgId, final String... args) {
 119         try {
 120             return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args);
 121         } catch (final java.util.MissingResourceException e) {
 122             throw new RuntimeException("no message resource found for message id: "+ msgId);
 123         }
 124     }
 125 
 126     // load engine.js and return content as a char[]
 127     @SuppressWarnings("resource")
 128     private static char[] loadEngineJSSource() {
 129         final String script = "resources/engine.js";
 130         try {
 131             final InputStream is = AccessController.doPrivileged(
 132                     new PrivilegedExceptionAction<InputStream>() {
 133                         @Override
 134                         public InputStream run() throws Exception {
 135                             final URL url = NashornScriptEngine.class.getResource(script);
 136                             return url.openStream();
 137                         }
 138                     });
 139             return Source.readFully(new InputStreamReader(is));
 140         } catch (final PrivilegedActionException | IOException e) {
 141             if (Context.DEBUG) {
 142                 e.printStackTrace();
 143             }
 144             throw new RuntimeException(e);
 145         }
 146     }
 147 
 148     // Source object for engine.js
 149     private static final Source ENGINE_SCRIPT_SRC = new Source(NashornException.ENGINE_SCRIPT_SOURCE_NAME, loadEngineJSSource());
 150 
 151     NashornScriptEngine(final NashornScriptEngineFactory factory, final ClassLoader appLoader) {
 152         this(factory, DEFAULT_OPTIONS, appLoader);
 153     }
 154 
 155     NashornScriptEngine(final NashornScriptEngineFactory factory, final String[] args, final ClassLoader appLoader) {
 156         this.factory = factory;
 157         final Options options = new Options("nashorn");
 158         options.process(args);
 159 
 160         // throw ParseException on first error from script
 161         final ErrorManager errMgr = new Context.ThrowErrorManager();
 162         // create new Nashorn Context
 163         this.nashornContext = AccessController.doPrivileged(new PrivilegedAction<Context>() {
 164             @Override
 165             public Context run() {
 166                 try {
 167                     return new Context(options, errMgr, appLoader);
 168                 } catch (final RuntimeException e) {
 169                     if (Context.DEBUG) {
 170                         e.printStackTrace();
 171                     }
 172                     throw e;
 173                 }
 174             }
 175         }, CREATE_CONTEXT_ACC_CTXT);
 176 
 177         // cache this option that is used often
 178         this._global_per_engine = nashornContext.getEnv()._global_per_engine;
 179 
 180         // create new global object
 181         this.global = createNashornGlobal(context);
 182         // set the default ENGINE_SCOPE object for the default context
 183         context.setBindings(new ScriptObjectMirror(global, global), ScriptContext.ENGINE_SCOPE);
 184     }
 185 
 186     @Override
 187     public Object eval(final Reader reader, final ScriptContext ctxt) throws ScriptException {
 188         return evalImpl(makeSource(reader, ctxt), ctxt);
 189     }
 190 
 191     @Override
 192     public Object eval(final String script, final ScriptContext ctxt) throws ScriptException {
 193         return evalImpl(makeSource(script, ctxt), ctxt);
 194     }
 195 
 196     @Override
 197     public ScriptEngineFactory getFactory() {
 198         return factory;
 199     }
 200 
 201     @Override
 202     public Bindings createBindings() {
 203         if (_global_per_engine) {
 204             // just create normal SimpleBindings.
 205             // We use same 'global' for all Bindings.
 206             return new SimpleBindings();
 207         }
 208         return createGlobalMirror(null);
 209     }
 210 
 211     // Compilable methods
 212 
 213     @Override
 214     public CompiledScript compile(final Reader reader) throws ScriptException {
 215         return asCompiledScript(makeSource(reader, context));
 216     }
 217 
 218     @Override
 219     public CompiledScript compile(final String str) throws ScriptException {
 220         return asCompiledScript(makeSource(str, context));
 221     }
 222 
 223     // Invocable methods
 224 
 225     @Override
 226     public Object invokeFunction(final String name, final Object... args)
 227             throws ScriptException, NoSuchMethodException {
 228         return invokeImpl(null, name, args);
 229     }
 230 
 231     @Override
 232     public Object invokeMethod(final Object thiz, final String name, final Object... args)
 233             throws ScriptException, NoSuchMethodException {
 234         if (thiz == null) {
 235             throw new IllegalArgumentException(getMessage("thiz.cannot.be.null"));
 236         }
 237         return invokeImpl(thiz, name, args);
 238     }
 239 
 240     @Override
 241     public <T> T getInterface(final Class<T> clazz) {
 242         return getInterfaceInner(null, clazz);
 243     }
 244 
 245     @Override
 246     public <T> T getInterface(final Object thiz, final Class<T> clazz) {
 247         if (thiz == null) {
 248             throw new IllegalArgumentException(getMessage("thiz.cannot.be.null"));
 249         }
 250         return getInterfaceInner(thiz, clazz);
 251     }
 252 
 253     // These are called from the "engine.js" script
 254 
 255     /**
 256      * This hook is used to search js global variables exposed from Java code.
 257      *
 258      * @param self 'this' passed from the script
 259      * @param ctxt current ScriptContext in which name is searched
 260      * @param name name of the variable searched
 261      * @return the value of the named variable
 262      */
 263     public Object __noSuchProperty__(final Object self, final ScriptContext ctxt, final String name) {
 264         if (ctxt != null) {
 265             final int scope = ctxt.getAttributesScope(name);
 266             final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
 267             if (scope != -1) {
 268                 return ScriptObjectMirror.unwrap(ctxt.getAttribute(name, scope), ctxtGlobal);
 269             }
 270 
 271             if (self == UNDEFINED) {
 272                 // scope access and so throw ReferenceError
 273                 throw referenceError(ctxtGlobal, "not.defined", name);
 274             }
 275         }
 276 
 277         return UNDEFINED;
 278     }
 279 
 280     // Implementation only below this point
 281 
 282     private static Source makeSource(final Reader reader, final ScriptContext ctxt) throws ScriptException {
 283         try {
 284             if (reader instanceof URLReader) {
 285                 final URL url = ((URLReader)reader).getURL();
 286                 final Charset cs = ((URLReader)reader).getCharset();
 287                 return new Source(url.toString(), url, cs);
 288             }
 289             return new Source(getScriptName(ctxt), Source.readFully(reader));
 290         } catch (final IOException e) {
 291             throw new ScriptException(e);
 292         }
 293     }
 294 
 295     private static Source makeSource(final String src, final ScriptContext ctxt) {
 296         return new Source(getScriptName(ctxt), src);
 297     }
 298 
 299     private static String getScriptName(final ScriptContext ctxt) {
 300         final Object val = ctxt.getAttribute(ScriptEngine.FILENAME);
 301         return (val != null) ? val.toString() : "<eval>";
 302     }
 303 
 304     private <T> T getInterfaceInner(final Object thiz, final Class<T> clazz) {
 305         if (clazz == null || !clazz.isInterface()) {
 306             throw new IllegalArgumentException(getMessage("interface.class.expected"));
 307         }
 308 
 309         // perform security access check as early as possible
 310         final SecurityManager sm = System.getSecurityManager();
 311         if (sm != null) {
 312             if (! Modifier.isPublic(clazz.getModifiers())) {
 313                 throw new SecurityException(getMessage("implementing.non.public.interface", clazz.getName()));
 314             }
 315             Context.checkPackageAccess(clazz);
 316         }
 317 
 318         ScriptObject realSelf = null;
 319         ScriptObject realGlobal = null;
 320         if(thiz == null) {
 321             // making interface out of global functions
 322             realSelf = realGlobal = getNashornGlobalFrom(context);
 323         } else if (thiz instanceof ScriptObjectMirror) {
 324             final ScriptObjectMirror mirror = (ScriptObjectMirror)thiz;
 325             realSelf = mirror.getScriptObject();
 326             realGlobal = mirror.getHomeGlobal();
 327             if (! isOfContext(realGlobal, nashornContext)) {
 328                 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
 329             }
 330         } else if (thiz instanceof ScriptObject) {
 331             // called from script code.
 332             realSelf = (ScriptObject)thiz;
 333             realGlobal = Context.getGlobal();
 334             if (realGlobal == null) {
 335                 throw new IllegalArgumentException(getMessage("no.current.nashorn.global"));
 336             }
 337 
 338             if (! isOfContext(realGlobal, nashornContext)) {
 339                 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
 340             }
 341         }
 342 
 343         if (realSelf == null) {
 344             throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
 345         }
 346 
 347         try {
 348             final ScriptObject oldGlobal = Context.getGlobal();
 349             final boolean globalChanged = (oldGlobal != realGlobal);
 350             try {
 351                 if (globalChanged) {
 352                     Context.setGlobal(realGlobal);
 353                 }
 354 
 355                 if (! isInterfaceImplemented(clazz, realSelf)) {
 356                     return null;
 357                 }
 358                 return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz).invoke(realSelf));
 359             } finally {
 360                 if (globalChanged) {
 361                     Context.setGlobal(oldGlobal);
 362                 }
 363             }
 364         } catch(final RuntimeException|Error e) {
 365             throw e;
 366         } catch(final Throwable t) {
 367             throw new RuntimeException(t);
 368         }
 369     }
 370 
 371     // Retrieve nashorn Global object for a given ScriptContext object
 372     private ScriptObject getNashornGlobalFrom(final ScriptContext ctxt) {
 373         if (_global_per_engine) {
 374             // shared single global object for all ENGINE_SCOPE Bindings
 375             return global;
 376         }
 377 
 378         final Bindings bindings = ctxt.getBindings(ScriptContext.ENGINE_SCOPE);
 379         // is this Nashorn's own Bindings implementation?
 380         if (bindings instanceof ScriptObjectMirror) {
 381             final ScriptObject sobj = globalFromMirror((ScriptObjectMirror)bindings);
 382             if (sobj != null) {
 383                 return sobj;
 384             }
 385         }
 386 
 387         // Arbitrary user Bindings implementation. Look for NASHORN_GLOBAL in it!
 388         Object scope = bindings.get(NASHORN_GLOBAL);
 389         if (scope instanceof ScriptObjectMirror) {
 390             final ScriptObject sobj = globalFromMirror((ScriptObjectMirror)scope);
 391             if (sobj != null) {
 392                 return sobj;
 393             }
 394         }
 395 
 396         // We didn't find associated nashorn global mirror in the Bindings given!
 397         // Create new global instance mirror and associate with the Bindings.
 398         final ScriptObjectMirror mirror = createGlobalMirror(ctxt);
 399         bindings.put(NASHORN_GLOBAL, mirror);
 400         return mirror.getScriptObject();
 401     }
 402 
 403     // Retrieve nashorn Global object from a given ScriptObjectMirror
 404     private ScriptObject globalFromMirror(final ScriptObjectMirror mirror) {
 405         ScriptObject sobj = mirror.getScriptObject();
 406         if (sobj instanceof GlobalObject && isOfContext(sobj, nashornContext)) {
 407             return sobj;
 408         }
 409 
 410         return null;
 411     }
 412 
 413     // Create a new ScriptObjectMirror wrapping a newly created Nashorn Global object
 414     private ScriptObjectMirror createGlobalMirror(final ScriptContext ctxt) {
 415         final ScriptObject newGlobal = createNashornGlobal(ctxt);
 416         return new ScriptObjectMirror(newGlobal, newGlobal);
 417     }
 418 
 419     // Create a new Nashorn Global object
 420     private ScriptObject createNashornGlobal(final ScriptContext ctxt) {
 421         final ScriptObject newGlobal = AccessController.doPrivileged(new PrivilegedAction<ScriptObject>() {
 422             @Override
 423             public ScriptObject run() {
 424                 try {
 425                     return nashornContext.newGlobal();
 426                 } catch (final RuntimeException e) {
 427                     if (Context.DEBUG) {
 428                         e.printStackTrace();
 429                     }
 430                     throw e;
 431                 }
 432             }
 433         }, CREATE_GLOBAL_ACC_CTXT);
 434 
 435         nashornContext.initGlobal(newGlobal);
 436 
 437         final int NON_ENUMERABLE_CONSTANT = Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE | Property.NOT_WRITABLE;
 438         // current ScriptContext exposed as "context"
 439         // "context" is non-writable from script - but script engine still
 440         // needs to set it and so save the context Property object
 441         contextProperty = newGlobal.addOwnProperty("context", NON_ENUMERABLE_CONSTANT, ctxt);
 442         // current ScriptEngine instance exposed as "engine". We added @SuppressWarnings("LeakingThisInConstructor") as
 443         // NetBeans identifies this assignment as such a leak - this is a false positive as we're setting this property
 444         // in the Global of a Context we just created - both the Context and the Global were just created and can not be
 445         // seen from another thread outside of this constructor.
 446         newGlobal.addOwnProperty("engine", NON_ENUMERABLE_CONSTANT, this);
 447         // global script arguments with undefined value
 448         newGlobal.addOwnProperty("arguments", Property.NOT_ENUMERABLE, UNDEFINED);
 449         // file name default is null
 450         newGlobal.addOwnProperty(ScriptEngine.FILENAME, Property.NOT_ENUMERABLE, null);
 451         // evaluate engine.js initialization script this new global object
 452         try {
 453             evalImpl(compileImpl(ENGINE_SCRIPT_SRC, newGlobal), ctxt, newGlobal);
 454         } catch (final ScriptException exp) {
 455             throw new RuntimeException(exp);
 456         }
 457         return newGlobal;
 458     }
 459 
 460     // scripts should see "context" and "engine" as variables in the given global object
 461     private void setContextVariables(final ScriptObject ctxtGlobal, final ScriptContext ctxt) {
 462         // set "context" global variable via contextProperty - because this
 463         // property is non-writable
 464         contextProperty.setObjectValue(ctxtGlobal, ctxtGlobal, ctxt, false);
 465         Object args = ScriptObjectMirror.unwrap(ctxt.getAttribute("arguments"), ctxtGlobal);
 466         if (args == null || args == UNDEFINED) {
 467             args = ScriptRuntime.EMPTY_ARRAY;
 468         }
 469         // if no arguments passed, expose it
 470         if (! (args instanceof ScriptObject)) {
 471             args = ((GlobalObject)ctxtGlobal).wrapAsObject(args);
 472             ctxtGlobal.set("arguments", args, false);
 473         }
 474     }
 475 
 476     private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
 477         name.getClass(); // null check
 478 
 479         ScriptObject invokeGlobal = null;
 480         ScriptObjectMirror selfMirror = null;
 481         if (selfObject instanceof ScriptObjectMirror) {
 482             selfMirror = (ScriptObjectMirror)selfObject;
 483             if (! isOfContext(selfMirror.getHomeGlobal(), nashornContext)) {
 484                 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
 485             }
 486             invokeGlobal = selfMirror.getHomeGlobal();
 487         } else if (selfObject instanceof ScriptObject) {
 488             // invokeMethod called from script code - in which case we may get 'naked' ScriptObject
 489             // Wrap it with oldGlobal to make a ScriptObjectMirror for the same.
 490             final ScriptObject oldGlobal = Context.getGlobal();
 491             invokeGlobal = oldGlobal;
 492             if (oldGlobal == null) {
 493                 throw new IllegalArgumentException(getMessage("no.current.nashorn.global"));
 494             }
 495 
 496             if (! isOfContext(oldGlobal, nashornContext)) {
 497                 throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
 498             }
 499 
 500             selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(selfObject, oldGlobal);
 501         } else if (selfObject == null) {
 502             // selfObject is null => global function call
 503             final ScriptObject ctxtGlobal = getNashornGlobalFrom(context);
 504             invokeGlobal = ctxtGlobal;
 505             selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(ctxtGlobal, ctxtGlobal);
 506         }
 507 
 508         if (selfMirror != null) {
 509             try {
 510                 return ScriptObjectMirror.translateUndefined(selfMirror.callMember(name, args));
 511             } catch (final Exception e) {
 512                 final Throwable cause = e.getCause();
 513                 if (cause instanceof NoSuchMethodException) {
 514                     throw (NoSuchMethodException)cause;
 515                 }
 516                 throwAsScriptException(e, invokeGlobal);
 517                 throw new AssertionError("should not reach here");
 518             }
 519         }
 520 
 521         // Non-script object passed as selfObject
 522         throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
 523     }
 524 
 525     private Object evalImpl(final Source src, final ScriptContext ctxt) throws ScriptException {
 526         return evalImpl(compileImpl(src, ctxt), ctxt);
 527     }
 528 
 529     private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException {
 530         return evalImpl(script, ctxt, getNashornGlobalFrom(ctxt));
 531     }
 532 
 533     private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt, final ScriptObject ctxtGlobal) throws ScriptException {
 534         if (script == null) {
 535             return null;
 536         }
 537         final ScriptObject oldGlobal = Context.getGlobal();
 538         final boolean globalChanged = (oldGlobal != ctxtGlobal);
 539         try {
 540             if (globalChanged) {
 541                 Context.setGlobal(ctxtGlobal);
 542             }
 543 
 544             // set ScriptContext variables if ctxt is non-null
 545             if (ctxt != null) {
 546                 setContextVariables(ctxtGlobal, ctxt);
 547             }
 548             return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal));
 549         } catch (final Exception e) {
 550             throwAsScriptException(e, ctxtGlobal);
 551             throw new AssertionError("should not reach here");
 552         } finally {
 553             if (globalChanged) {
 554                 Context.setGlobal(oldGlobal);
 555             }
 556         }
 557     }
 558 
 559     private static void throwAsScriptException(final Exception e, final ScriptObject global) throws ScriptException {
 560         if (e instanceof ScriptException) {
 561             throw (ScriptException)e;
 562         } else if (e instanceof NashornException) {
 563             final NashornException ne = (NashornException)e;
 564             final ScriptException se = new ScriptException(
 565                 ne.getMessage(), ne.getFileName(),
 566                 ne.getLineNumber(), ne.getColumnNumber());
 567             ne.initEcmaError(global);
 568             se.initCause(e);
 569             throw se;
 570         } else if (e instanceof RuntimeException) {
 571             throw (RuntimeException)e;
 572         } else {
 573             // wrap any other exception as ScriptException
 574             throw new ScriptException(e);
 575         }
 576     }
 577 
 578     private CompiledScript asCompiledScript(final Source source) throws ScriptException {
 579         final ScriptFunction func = compileImpl(source, context);
 580         return new CompiledScript() {
 581             @Override
 582             public Object eval(final ScriptContext ctxt) throws ScriptException {
 583                 final ScriptObject globalObject = getNashornGlobalFrom(ctxt);
 584                 // Are we running the script in the correct global?
 585                 if (func.getScope() == globalObject) {
 586                     return evalImpl(func, ctxt, globalObject);
 587                 }
 588                 // ScriptContext with a different global. Compile again!
 589                 // Note that we may still hit per-global compilation cache.
 590                 return evalImpl(compileImpl(source, ctxt), ctxt, globalObject);
 591             }
 592             @Override
 593             public ScriptEngine getEngine() {
 594                 return NashornScriptEngine.this;
 595             }
 596         };
 597     }
 598 
 599     private ScriptFunction compileImpl(final Source source, final ScriptContext ctxt) throws ScriptException {
 600         return compileImpl(source, getNashornGlobalFrom(ctxt));
 601     }
 602 
 603     private ScriptFunction compileImpl(final Source source, final ScriptObject newGlobal) throws ScriptException {
 604         final ScriptObject oldGlobal = Context.getGlobal();
 605         final boolean globalChanged = (oldGlobal != newGlobal);
 606         try {
 607             if (globalChanged) {
 608                 Context.setGlobal(newGlobal);
 609             }
 610 
 611             return nashornContext.compileScript(source, newGlobal);
 612         } catch (final Exception e) {
 613             throwAsScriptException(e, newGlobal);
 614             throw new AssertionError("should not reach here");
 615         } finally {
 616             if (globalChanged) {
 617                 Context.setGlobal(oldGlobal);
 618             }
 619         }
 620     }
 621 
 622     private static boolean isInterfaceImplemented(final Class<?> iface, final ScriptObject sobj) {
 623         for (final Method method : iface.getMethods()) {
 624             // ignore methods of java.lang.Object class
 625             if (method.getDeclaringClass() == Object.class) {
 626                 continue;
 627             }
 628 
 629             Object obj = sobj.get(method.getName());
 630             if (! (obj instanceof ScriptFunction)) {
 631                 return false;
 632             }
 633         }
 634         return true;
 635     }
 636 
 637     private static boolean isOfContext(final ScriptObject global, final Context context) {
 638         assert global instanceof GlobalObject: "Not a Global object";
 639         return ((GlobalObject)global).isOfContext(context);
 640     }
 641 }