8021361: ClassCastException:.ScriptObjectMirror -> ScriptObject when getInterface called on object from different ScriptContext

Reviewed-by: jlaskey, attila
This commit is contained in:
Athijegannathan Sundararajan 2013-07-25 20:10:48 +05:30
parent 55483aa548
commit fdb4922746
5 changed files with 254 additions and 25 deletions

View File

@ -40,6 +40,9 @@ import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.script.AbstractScriptEngine;
import javax.script.Bindings;
import javax.script.Compilable;
@ -79,6 +82,28 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
// default options passed to Nashorn Options object
private static final String[] DEFAULT_OPTIONS = new String[] { "-scripting", "-doe" };
private static final String MESSAGES_RESOURCE = "jdk.nashorn.api.scripting.resources.Messages";
// Without do privileged, under security manager messages can not be loaded.
private static final ResourceBundle MESSAGES_BUNDLE;
static {
MESSAGES_BUNDLE = AccessController.doPrivileged(
new PrivilegedAction<ResourceBundle>() {
@Override
public ResourceBundle run() {
return ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault());
}
});
}
private static String getMessage(final String msgId, final String... args) {
try {
return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args);
} catch (final java.util.MissingResourceException e) {
throw new RuntimeException("no message resource found for message id: "+ msgId);
}
}
NashornScriptEngine(final NashornScriptEngineFactory factory, final ClassLoader appLoader) {
this(factory, DEFAULT_OPTIONS, appLoader);
}
@ -176,43 +201,63 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
}
@Override
public Object invokeMethod(final Object self, final String name, final Object... args)
public Object invokeMethod(final Object thiz, final String name, final Object... args)
throws ScriptException, NoSuchMethodException {
if (self == null) {
throw new IllegalArgumentException("script object can not be null");
if (thiz == null) {
throw new IllegalArgumentException(getMessage("thiz.cannot.be.null"));
}
return invokeImpl(self, name, args);
return invokeImpl(thiz, name, args);
}
private <T> T getInterfaceInner(final Object self, final Class<T> clazz) {
private <T> T getInterfaceInner(final Object thiz, final Class<T> clazz) {
if (clazz == null || !clazz.isInterface()) {
throw new IllegalArgumentException("interface Class expected");
throw new IllegalArgumentException(getMessage("interface.class.expected"));
}
// perform security access check as early as possible
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
if (! Modifier.isPublic(clazz.getModifiers())) {
throw new SecurityException("attempt to implement non-public interfce: " + clazz);
throw new SecurityException(getMessage("implementing.non.public.interface", clazz.getName()));
}
Context.checkPackageAccess(clazz.getName());
}
final ScriptObject realSelf;
final ScriptObject ctxtGlobal = getNashornGlobalFrom(context);
if(self == null) {
realSelf = ctxtGlobal;
} else if (!(self instanceof ScriptObject)) {
realSelf = (ScriptObject)ScriptObjectMirror.unwrap(self, ctxtGlobal);
} else {
realSelf = (ScriptObject)self;
ScriptObject realSelf = null;
ScriptObject realGlobal = null;
if(thiz == null) {
// making interface out of global functions
realSelf = realGlobal = getNashornGlobalFrom(context);
} else if (thiz instanceof ScriptObjectMirror) {
final ScriptObjectMirror mirror = (ScriptObjectMirror)thiz;
realSelf = mirror.getScriptObject();
realGlobal = mirror.getHomeGlobal();
if (! realGlobal.isOfContext(nashornContext)) {
throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
}
} else if (thiz instanceof ScriptObject) {
// called from script code.
realSelf = (ScriptObject)thiz;
realGlobal = Context.getGlobal();
if (realGlobal == null) {
throw new IllegalArgumentException(getMessage("no.current.nashorn.global"));
}
if (! realGlobal.isOfContext(nashornContext)) {
throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
}
}
if (realSelf == null) {
throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
}
try {
final ScriptObject oldGlobal = Context.getGlobal();
final boolean globalChanged = (oldGlobal != realGlobal);
try {
if(oldGlobal != ctxtGlobal) {
Context.setGlobal(ctxtGlobal);
if (globalChanged) {
Context.setGlobal(realGlobal);
}
if (! isInterfaceImplemented(clazz, realSelf)) {
@ -220,7 +265,7 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
}
return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz).invoke(realSelf));
} finally {
if(oldGlobal != ctxtGlobal) {
if (globalChanged) {
Context.setGlobal(oldGlobal);
}
}
@ -237,11 +282,11 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
}
@Override
public <T> T getInterface(final Object self, final Class<T> clazz) {
if (self == null) {
throw new IllegalArgumentException("script object can not be null");
public <T> T getInterface(final Object thiz, final Class<T> clazz) {
if (thiz == null) {
throw new IllegalArgumentException(getMessage("thiz.cannot.be.null"));
}
return getInterfaceInner(self, clazz);
return getInterfaceInner(thiz, clazz);
}
// These are called from the "engine.js" script
@ -362,13 +407,22 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
ScriptObjectMirror selfMirror = null;
if (selfObject instanceof ScriptObjectMirror) {
selfMirror = (ScriptObjectMirror)selfObject;
if (! selfMirror.getHomeGlobal().isOfContext(nashornContext)) {
throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
}
} else if (selfObject instanceof ScriptObject) {
// invokeMethod called from script code - in which case we may get 'naked' ScriptObject
// Wrap it with oldGlobal to make a ScriptObjectMirror for the same.
final ScriptObject oldGlobal = Context.getGlobal();
if (oldGlobal != null) {
selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(selfObject, oldGlobal);
if (oldGlobal == null) {
throw new IllegalArgumentException(getMessage("no.current.nashorn.global"));
}
if (! oldGlobal.isOfContext(nashornContext)) {
throw new IllegalArgumentException(getMessage("script.object.from.another.engine"));
}
selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(selfObject, oldGlobal);
} else if (selfObject == null) {
// selfObject is null => global function call
final ScriptObject ctxtGlobal = getNashornGlobalFrom(context);
@ -389,7 +443,7 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
}
// Non-script object passed as selfObject
throw new IllegalArgumentException("can not call invokeMethod on non-script objects");
throw new IllegalArgumentException(getMessage("interface.on.non.script.object"));
}
private Object evalImpl(final char[] buf, final ScriptContext ctxt) throws ScriptException {

View File

@ -599,14 +599,22 @@ public final class ScriptObjectMirror extends JSObject implements Bindings {
// package-privates below this.
ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) {
assert sobj != null : "ScriptObjectMirror on null!";
assert global != null : "null global for ScriptObjectMirror!";
this.sobj = sobj;
this.global = global;
}
// accessors for script engine
ScriptObject getScriptObject() {
return sobj;
}
ScriptObject getHomeGlobal() {
return global;
}
static Object translateUndefined(Object obj) {
return (obj == ScriptRuntime.UNDEFINED)? null : obj;
}

View File

@ -0,0 +1,32 @@
#
# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
thiz.cannot.be.null=script object 'this' for getMethod, getInterface calls can not be null
interface.class.expected=interface Class expected in getInterface
interface.on.non.script.object=getInterface cannot be called on non-script object
no.current.nashorn.global=no current Global instance for nashorn
implementing.non.public.interface=Cannot implement non-public interface: {0}
script.object.from.another.engine=Script object belongs to another script engine

View File

@ -1045,6 +1045,15 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr
return getContext()._strict;
}
/**
* Checks if this object belongs to the given context
* @param ctx context to check against
* @return true if this object belongs to the given context
*/
public final boolean isOfContext(final Context ctx) {
return context == ctx;
}
/**
* Return the current context from the object's map.
* @return Current context.

View File

@ -362,6 +362,90 @@ public class ScriptEngineTest {
}
}
@Test
/**
* Check that we can get interface out of a script object even after
* switching to use different ScriptContext.
*/
public void getInterfaceDifferentContext() {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
try {
Object obj = e.eval("({ run: function() { } })");
// change script context
ScriptContext ctxt = new SimpleScriptContext();
ctxt.setBindings(e.createBindings(), ScriptContext.ENGINE_SCOPE);
e.setContext(ctxt);
Runnable r = ((Invocable)e).getInterface(obj, Runnable.class);
r.run();
}catch (final Exception exp) {
exp.printStackTrace();
fail(exp.getMessage());
}
}
@Test
/**
* Check that getInterface on non-script object 'thiz' results in IllegalArgumentException.
*/
public void getInterfaceNonScriptObjectThizTest() {
final ScriptEngineManager m = new ScriptEngineManager();
final ScriptEngine e = m.getEngineByName("nashorn");
try {
((Invocable)e).getInterface(new Object(), Runnable.class);
fail("should have thrown IllegalArgumentException");
} catch (final Exception exp) {
if (! (exp instanceof IllegalArgumentException)) {
exp.printStackTrace();
fail(exp.getMessage());
}
}
}
@Test
/**
* Check that getInterface on null 'thiz' results in IllegalArgumentException.
*/
public void getInterfaceNullThizTest() {
final ScriptEngineManager m = new ScriptEngineManager();
final ScriptEngine e = m.getEngineByName("nashorn");
try {
((Invocable)e).getInterface(null, Runnable.class);
fail("should have thrown IllegalArgumentException");
} catch (final Exception exp) {
if (! (exp instanceof IllegalArgumentException)) {
exp.printStackTrace();
fail(exp.getMessage());
}
}
}
@Test
/**
* Check that calling getInterface on mirror created by another engine results in IllegalArgumentException.
*/
public void getInterfaceMixEnginesTest() {
final ScriptEngineManager m = new ScriptEngineManager();
final ScriptEngine engine1 = m.getEngineByName("nashorn");
final ScriptEngine engine2 = m.getEngineByName("nashorn");
try {
Object obj = engine1.eval("({ run: function() {} })");
// pass object from engine1 to engine2 as 'thiz' for getInterface
((Invocable)engine2).getInterface(obj, Runnable.class);
fail("should have thrown IllegalArgumentException");
} catch (final Exception exp) {
if (! (exp instanceof IllegalArgumentException)) {
exp.printStackTrace();
fail(exp.getMessage());
}
}
}
@Test
public void accessGlobalTest() {
final ScriptEngineManager m = new ScriptEngineManager();
@ -733,6 +817,48 @@ public class ScriptEngineTest {
}
}
@Test
/**
* Check that calling method on null 'thiz' results in IllegalArgumentException.
*/
public void invokeMethodNullThizTest() {
final ScriptEngineManager m = new ScriptEngineManager();
final ScriptEngine e = m.getEngineByName("nashorn");
try {
((Invocable)e).invokeMethod(null, "toString");
fail("should have thrown IllegalArgumentException");
} catch (final Exception exp) {
if (! (exp instanceof IllegalArgumentException)) {
exp.printStackTrace();
fail(exp.getMessage());
}
}
}
@Test
/**
* Check that calling method on mirror created by another engine results in IllegalArgumentException.
*/
public void invokeMethodMixEnginesTest() {
final ScriptEngineManager m = new ScriptEngineManager();
final ScriptEngine engine1 = m.getEngineByName("nashorn");
final ScriptEngine engine2 = m.getEngineByName("nashorn");
try {
Object obj = engine1.eval("({ run: function() {} })");
// pass object from engine1 to engine2 as 'thiz' for invokeMethod
((Invocable)engine2).invokeMethod(obj, "run");
fail("should have thrown IllegalArgumentException");
} catch (final Exception exp) {
if (! (exp instanceof IllegalArgumentException)) {
exp.printStackTrace();
fail(exp.getMessage());
}
}
}
@Test
public void noEnumerablePropertiesTest() {
final ScriptEngineManager m = new ScriptEngineManager();