8006736: nashorn script engine should support the usage multiple global objects with same engine instance

Reviewed-by: lagergren, jlaskey, hannesw
This commit is contained in:
Athijegannathan Sundararajan 2013-01-23 17:04:02 +05:30
parent 7014b0cced
commit 4135674f92
3 changed files with 143 additions and 62 deletions

View File

@ -47,7 +47,6 @@ import javax.script.ScriptContext;
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 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
});
// 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 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
}
throw new RuntimeException(e);
}
context.setBindings(new ScriptObjectMirror(global, global), ScriptContext.ENGINE_SCOPE);
}
@Override
@ -170,7 +147,8 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
@Override
public Bindings createBindings() {
return new SimpleBindings();
final ScriptObject newGlobal = createNashornGlobal();
return new ScriptObjectMirror(newGlobal, newGlobal);
}
// Compilable methods
@ -208,22 +186,23 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
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 final class NashornScriptEngine extends AbstractScriptEngine implements C
*/
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,29 +262,72 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
*/
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(global, "not.a.function", ScriptRuntime.safeToString(name));
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;
}
}
// didn't find global object from context given - return the engine-wide global
return global;
}
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 {
evalSupportScript("resources/engine.js");
}
@ -332,51 +355,53 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
// 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 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
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 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
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 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
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 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
}
}
// 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;
}
});
}

View File

@ -329,6 +329,11 @@ final class ScriptObjectMirror extends JSObject implements Bindings {
});
}
// package-privates below this.
ScriptObject getScriptObject() {
return sobj;
}
static Object translateUndefined(Object obj) {
return (obj == ScriptRuntime.UNDEFINED)? null : obj;
}

View File

@ -46,8 +46,10 @@ import javax.script.ScriptEngine;
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 @@ public class ScriptEngineTest {
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());
}
}
}