8006736: nashorn script engine should support the usage multiple global objects with same engine instance
authorsundar
Wed, 23 Jan 2013 17:04:02 +0530
changeset 16189 38eaf01bf6d6
parent 16188 d6390b0ea32a
child 16190 23e52f635bb6
8006736: nashorn script engine should support the usage multiple global objects with same engine instance Reviewed-by: lagergren, jlaskey, hannesw
nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java
nashorn/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java
nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java
--- a/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java	Tue Jan 22 22:07:12 2013 +0530
+++ b/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java	Wed Jan 23 17:04:02 2013 +0530
@@ -47,7 +47,6 @@
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineFactory;
 import javax.script.ScriptException;
-import javax.script.SimpleBindings;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ErrorManager;
 import jdk.nashorn.internal.runtime.GlobalObject;
@@ -112,29 +111,9 @@
         });
 
         // create new global object
-        this.global =  AccessController.doPrivileged(new PrivilegedAction<ScriptObject>() {
-            @Override
-            public ScriptObject run() {
-                try {
-                    return nashornContext.createGlobal();
-                } catch (final RuntimeException e) {
-                    if (Context.DEBUG) {
-                        e.printStackTrace();
-                    }
-                    throw e;
-                }
-            }
-        });
-
-        // current ScriptContext exposed as "context"
-        global.addOwnProperty("context", Property.NOT_ENUMERABLE, context);
-        // current ScriptEngine instance exposed as "engine". We added @SuppressWarnings("LeakingThisInConstructor") as
-        // NetBeans identifies this assignment as such a leak - this is a false positive as we're setting this property
-        // in the Global of a Context we just created - both the Context and the Global were just created and can not be
-        // seen from another thread outside of this constructor.
-        global.addOwnProperty("engine", Property.NOT_ENUMERABLE, this);
-        // global script arguments
-        global.addOwnProperty("arguments", Property.NOT_ENUMERABLE, UNDEFINED);
+        this.global =  createNashornGlobal();
+        // set the default engine scope for the default context
+        context.setBindings(new ScriptObjectMirror(global, global), ScriptContext.ENGINE_SCOPE);
 
         // evaluate engine initial script
         try {
@@ -145,8 +124,6 @@
             }
             throw new RuntimeException(e);
         }
-
-        context.setBindings(new ScriptObjectMirror(global, global), ScriptContext.ENGINE_SCOPE);
     }
 
     @Override
@@ -170,7 +147,8 @@
 
     @Override
     public Bindings createBindings() {
-        return new SimpleBindings();
+        final ScriptObject newGlobal = createNashornGlobal();
+        return new ScriptObjectMirror(newGlobal, newGlobal);
     }
 
     // Compilable methods
@@ -208,22 +186,23 @@
 
     private <T> T getInterfaceInner(final Object self, final Class<T> clazz) {
         final Object realSelf;
+        final ScriptObject ctxtGlobal = getNashornGlobalFrom(context);
         if(self == null) {
-            realSelf = global;
+            realSelf = ctxtGlobal;
         } else if (!(self instanceof ScriptObject)) {
-            realSelf = ScriptObjectMirror.unwrap(self, global);
+            realSelf = ScriptObjectMirror.unwrap(self, ctxtGlobal);
         } else {
             realSelf = self;
         }
         try {
             final ScriptObject oldGlobal = getNashornGlobal();
             try {
-                if(oldGlobal != global) {
-                    setNashornGlobal(global);
+                if(oldGlobal != ctxtGlobal) {
+                    setNashornGlobal(ctxtGlobal);
                 }
                 return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz).invoke(realSelf));
             } finally {
-                if(oldGlobal != global) {
+                if(oldGlobal != ctxtGlobal) {
                     setNashornGlobal(oldGlobal);
                 }
             }
@@ -259,13 +238,14 @@
      */
     public Object __noSuchProperty__(final Object self, final ScriptContext ctxt, final String name) {
         final int scope = ctxt.getAttributesScope(name);
+        final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
         if (scope != -1) {
-            return ScriptObjectMirror.unwrap(ctxt.getAttribute(name, scope), global);
+            return ScriptObjectMirror.unwrap(ctxt.getAttribute(name, scope), ctxtGlobal);
         }
 
         if (self == UNDEFINED) {
             // scope access and so throw ReferenceError
-            referenceError(global, "not.defined", name);
+            referenceError(ctxtGlobal, "not.defined", name);
         }
 
         return UNDEFINED;
@@ -282,27 +262,70 @@
      */
     public Object __noSuchMethod__(final Object self, final ScriptContext ctxt, final String name, final Object args) {
         final int scope = ctxt.getAttributesScope(name);
+        final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
         Object value;
 
         if (scope != -1) {
             value = ctxt.getAttribute(name, scope);
         } else {
             if (self == UNDEFINED) {
-                referenceError(global, "not.defined", name);
+                referenceError(ctxtGlobal, "not.defined", name);
             } else {
-                typeError(global, "no.such.function", name, ScriptRuntime.safeToString(global));
+                typeError(ctxtGlobal, "no.such.function", name, ScriptRuntime.safeToString(ctxtGlobal));
             }
             return UNDEFINED;
         }
 
-        value = ScriptObjectMirror.unwrap(value, global);
+        value = ScriptObjectMirror.unwrap(value, ctxtGlobal);
         if (value instanceof ScriptFunction) {
-            return ScriptObjectMirror.unwrap(ScriptRuntime.apply((ScriptFunction)value, global, args), global);
+            return ScriptObjectMirror.unwrap(ScriptRuntime.apply((ScriptFunction)value, ctxtGlobal, args), ctxtGlobal);
+        }
+
+        typeError(ctxtGlobal, "not.a.function", ScriptRuntime.safeToString(name));
+
+        return UNDEFINED;
+    }
+
+    private ScriptObject getNashornGlobalFrom(final ScriptContext ctxt) {
+        final Bindings bindings = ctxt.getBindings(ScriptContext.ENGINE_SCOPE);
+        if (bindings instanceof ScriptObjectMirror) {
+             ScriptObject sobj = ((ScriptObjectMirror)bindings).getScriptObject();
+             if (sobj instanceof GlobalObject) {
+                 return sobj;
+             }
         }
 
-        typeError(global, "not.a.function", ScriptRuntime.safeToString(name));
+        // didn't find global object from context given - return the engine-wide global
+        return global;
+    }
 
-        return UNDEFINED;
+    private ScriptObject createNashornGlobal() {
+        final ScriptObject newGlobal = AccessController.doPrivileged(new PrivilegedAction<ScriptObject>() {
+            @Override
+            public ScriptObject run() {
+                try {
+                    return nashornContext.createGlobal();
+                } catch (final RuntimeException e) {
+                    if (Context.DEBUG) {
+                        e.printStackTrace();
+                    }
+                    throw e;
+                }
+            }
+        });
+
+        // current ScriptContext exposed as "context"
+        newGlobal.addOwnProperty("context", Property.NOT_ENUMERABLE, UNDEFINED);
+        // current ScriptEngine instance exposed as "engine". We added @SuppressWarnings("LeakingThisInConstructor") as
+        // NetBeans identifies this assignment as such a leak - this is a false positive as we're setting this property
+        // in the Global of a Context we just created - both the Context and the Global were just created and can not be
+        // seen from another thread outside of this constructor.
+        newGlobal.addOwnProperty("engine", Property.NOT_ENUMERABLE, this);
+        // global script arguments with undefined value
+        newGlobal.addOwnProperty("arguments", Property.NOT_ENUMERABLE, UNDEFINED);
+        // file name default is null
+        newGlobal.addOwnProperty(ScriptEngine.FILENAME, Property.NOT_ENUMERABLE, null);
+        return newGlobal;
     }
 
     private void evalEngineScript() throws ScriptException {
@@ -332,51 +355,53 @@
     // scripts should see "context" and "engine" as variables
     private void setContextVariables(final ScriptContext ctxt) {
         ctxt.setAttribute("context", ctxt, ScriptContext.ENGINE_SCOPE);
-        global.set("context", ctxt, false);
-        Object args = ScriptObjectMirror.unwrap(ctxt.getAttribute("arguments"), global);
+        final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
+        ctxtGlobal.set("context", ctxt, false);
+        Object args = ScriptObjectMirror.unwrap(ctxt.getAttribute("arguments"), ctxtGlobal);
         if (args == null || args == UNDEFINED) {
             args = ScriptRuntime.EMPTY_ARRAY;
         }
         // if no arguments passed, expose it
-        args = ((GlobalObject)global).wrapAsObject(args);
-        global.set("arguments", args, false);
+        args = ((GlobalObject)ctxtGlobal).wrapAsObject(args);
+        ctxtGlobal.set("arguments", args, false);
     }
 
     private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
         final ScriptObject oldGlobal     = getNashornGlobal();
-        final boolean globalChanged = (oldGlobal != global);
+        final ScriptObject ctxtGlobal    = getNashornGlobalFrom(context);
+        final boolean globalChanged = (oldGlobal != ctxtGlobal);
 
         Object self = selfObject;
 
         try {
             if (globalChanged) {
-                setNashornGlobal(global);
+                setNashornGlobal(ctxtGlobal);
             }
 
             ScriptObject sobj;
             Object       value = null;
 
-            self = ScriptObjectMirror.unwrap(self, global);
+            self = ScriptObjectMirror.unwrap(self, ctxtGlobal);
 
             // FIXME: should convert when self is not ScriptObject
             if (self instanceof ScriptObject) {
                 sobj = (ScriptObject)self;
                 value = sobj.get(name);
             } else if (self == null) {
-                self  = global;
-                sobj  = global;
+                self  = ctxtGlobal;
+                sobj  = ctxtGlobal;
                 value = sobj.get(name);
             }
 
             if (value instanceof ScriptFunction) {
                 final Object res;
                 try {
-                    res = ScriptRuntime.apply((ScriptFunction)value, self, ScriptObjectMirror.unwrapArray(args, global));
+                    res = ScriptRuntime.apply((ScriptFunction)value, self, ScriptObjectMirror.unwrapArray(args, ctxtGlobal));
                 } catch (final Exception e) {
                     throwAsScriptException(e);
                     throw new AssertionError("should not reach here");
                 }
-                return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(res, global));
+                return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(res, ctxtGlobal));
             }
 
             throw new NoSuchMethodException(name);
@@ -396,10 +421,11 @@
             return null;
         }
         final ScriptObject oldGlobal = getNashornGlobal();
-        final boolean globalChanged = (oldGlobal != global);
+        final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
+        final boolean globalChanged = (oldGlobal != ctxtGlobal);
         try {
             if (globalChanged) {
-                setNashornGlobal(global);
+                setNashornGlobal(ctxtGlobal);
             }
 
             setContextVariables(ctxt);
@@ -413,8 +439,8 @@
                 return null;
             }
 
-            Object res = ScriptRuntime.apply(script, global);
-            return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(res, global));
+            Object res = ScriptRuntime.apply(script, ctxtGlobal);
+            return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(res, ctxtGlobal));
         } catch (final Exception e) {
             throwAsScriptException(e);
             throw new AssertionError("should not reach here");
@@ -458,17 +484,18 @@
 
     private ScriptFunction compileImpl(final char[] buf, final ScriptContext ctxt) throws ScriptException {
         final ScriptObject oldGlobal = getNashornGlobal();
-        final boolean globalChanged = (oldGlobal != global);
+        final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
+        final boolean globalChanged = (oldGlobal != ctxtGlobal);
         try {
             final Object val = ctxt.getAttribute(ScriptEngine.FILENAME);
             final String fileName = (val != null) ? val.toString() : "<eval>";
 
             final Source source = new Source(fileName, buf);
             if (globalChanged) {
-                setNashornGlobal(global);
+                setNashornGlobal(ctxtGlobal);
             }
 
-            return nashornContext.compileScript(source, global, nashornContext._strict);
+            return nashornContext.compileScript(source, ctxtGlobal, nashornContext._strict);
         } catch (final Exception e) {
             throwAsScriptException(e);
             throw new AssertionError("should not reach here");
@@ -479,17 +506,17 @@
         }
     }
 
-    // don't make these public!!
+    // don't make this public!!
     static ScriptObject getNashornGlobal() {
-       return Context.getGlobal();
+        return Context.getGlobal();
     }
 
-    static void setNashornGlobal(final ScriptObject global) {
+    static void setNashornGlobal(final ScriptObject newGlobal) {
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
             @Override
             public Void run() {
-                Context.setGlobal(global);
-                return null;
+               Context.setGlobal(newGlobal);
+               return null;
             }
         });
     }
--- a/nashorn/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Tue Jan 22 22:07:12 2013 +0530
+++ b/nashorn/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Wed Jan 23 17:04:02 2013 +0530
@@ -329,6 +329,11 @@
         });
     }
 
+    // package-privates below this.
+    ScriptObject getScriptObject() {
+        return sobj;
+    }
+
     static Object translateUndefined(Object obj) {
         return (obj == ScriptRuntime.UNDEFINED)? null : obj;
     }
--- a/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java	Tue Jan 22 22:07:12 2013 +0530
+++ b/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java	Wed Jan 23 17:04:02 2013 +0530
@@ -46,8 +46,10 @@
 import javax.script.ScriptEngineFactory;
 import javax.script.ScriptEngineManager;
 import javax.script.ScriptException;
+import javax.script.SimpleScriptContext;
 import jdk.nashorn.internal.runtime.Version;
 import netscape.javascript.JSObject;
+import org.testng.Assert;
 import org.testng.TestNG;
 import org.testng.annotations.Test;
 
@@ -922,4 +924,51 @@
         assertEquals(engineScope.get("myVar"), "nashorn");
         assertEquals(e.get("myVar"), "nashorn");
     }
+
+    @Test
+    public void multiGlobalTest() {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        final Bindings b = e.createBindings();
+        final ScriptContext newCtxt = new SimpleScriptContext();
+        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
+
+        try {
+            Object obj1 = e.eval("Object");
+            Object obj2 = e.eval("Object", newCtxt);
+            Assert.assertNotEquals(obj1, obj2);
+            Assert.assertNotNull(obj1);
+            Assert.assertNotNull(obj2);
+            Assert.assertEquals(obj1.toString(), obj2.toString());
+
+            e.eval("x = 'hello'");
+            e.eval("x = 'world'", newCtxt);
+            Object x1 = e.getContext().getAttribute("x");
+            Object x2 = newCtxt.getAttribute("x");
+            Assert.assertNotEquals(x1, x2);
+            Assert.assertEquals(x1, "hello");
+            Assert.assertEquals(x2, "world");
+
+            x1 = e.eval("x");
+            x2 = e.eval("x", newCtxt);
+            Assert.assertNotEquals(x1, x2);
+            Assert.assertEquals(x1, "hello");
+            Assert.assertEquals(x2, "world");
+
+            final ScriptContext origCtxt = e.getContext();
+            e.setContext(newCtxt);
+            e.eval("y = new Object()");
+            e.eval("y = new Object()", origCtxt);
+
+            Object y1 = origCtxt.getAttribute("y");
+            Object y2 = newCtxt.getAttribute("y");
+            Assert.assertNotEquals(y1, y2);
+            Assert.assertNotEquals(e.eval("y"), e.eval("y", origCtxt));
+            Assert.assertEquals("[object Object]", y1.toString());
+            Assert.assertEquals("[object Object]", y2.toString());
+        } catch (final ScriptException se) {
+            se.printStackTrace();
+            fail(se.getMessage());
+        }
+    }
 }