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