Thu, 25 Jul 2013 20:10:48 +0530
8021361: ClassCastException:.ScriptObjectMirror -> ScriptObject when getInterface called on object from different ScriptContext
Reviewed-by: jlaskey, attila
1.1 --- a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java Thu Jul 25 11:56:12 2013 +0200 1.2 +++ b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java Thu Jul 25 20:10:48 2013 +0530 1.3 @@ -40,6 +40,9 @@ 1.4 import java.security.PrivilegedAction; 1.5 import java.security.PrivilegedActionException; 1.6 import java.security.PrivilegedExceptionAction; 1.7 +import java.text.MessageFormat; 1.8 +import java.util.Locale; 1.9 +import java.util.ResourceBundle; 1.10 import javax.script.AbstractScriptEngine; 1.11 import javax.script.Bindings; 1.12 import javax.script.Compilable; 1.13 @@ -79,6 +82,28 @@ 1.14 // default options passed to Nashorn Options object 1.15 private static final String[] DEFAULT_OPTIONS = new String[] { "-scripting", "-doe" }; 1.16 1.17 + private static final String MESSAGES_RESOURCE = "jdk.nashorn.api.scripting.resources.Messages"; 1.18 + 1.19 + // Without do privileged, under security manager messages can not be loaded. 1.20 + private static final ResourceBundle MESSAGES_BUNDLE; 1.21 + static { 1.22 + MESSAGES_BUNDLE = AccessController.doPrivileged( 1.23 + new PrivilegedAction<ResourceBundle>() { 1.24 + @Override 1.25 + public ResourceBundle run() { 1.26 + return ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault()); 1.27 + } 1.28 + }); 1.29 + } 1.30 + 1.31 + private static String getMessage(final String msgId, final String... args) { 1.32 + try { 1.33 + return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args); 1.34 + } catch (final java.util.MissingResourceException e) { 1.35 + throw new RuntimeException("no message resource found for message id: "+ msgId); 1.36 + } 1.37 + } 1.38 + 1.39 NashornScriptEngine(final NashornScriptEngineFactory factory, final ClassLoader appLoader) { 1.40 this(factory, DEFAULT_OPTIONS, appLoader); 1.41 } 1.42 @@ -176,43 +201,63 @@ 1.43 } 1.44 1.45 @Override 1.46 - public Object invokeMethod(final Object self, final String name, final Object... args) 1.47 + public Object invokeMethod(final Object thiz, final String name, final Object... args) 1.48 throws ScriptException, NoSuchMethodException { 1.49 - if (self == null) { 1.50 - throw new IllegalArgumentException("script object can not be null"); 1.51 + if (thiz == null) { 1.52 + throw new IllegalArgumentException(getMessage("thiz.cannot.be.null")); 1.53 } 1.54 - return invokeImpl(self, name, args); 1.55 + return invokeImpl(thiz, name, args); 1.56 } 1.57 1.58 - private <T> T getInterfaceInner(final Object self, final Class<T> clazz) { 1.59 + private <T> T getInterfaceInner(final Object thiz, final Class<T> clazz) { 1.60 if (clazz == null || !clazz.isInterface()) { 1.61 - throw new IllegalArgumentException("interface Class expected"); 1.62 + throw new IllegalArgumentException(getMessage("interface.class.expected")); 1.63 } 1.64 1.65 // perform security access check as early as possible 1.66 final SecurityManager sm = System.getSecurityManager(); 1.67 if (sm != null) { 1.68 if (! Modifier.isPublic(clazz.getModifiers())) { 1.69 - throw new SecurityException("attempt to implement non-public interfce: " + clazz); 1.70 + throw new SecurityException(getMessage("implementing.non.public.interface", clazz.getName())); 1.71 } 1.72 Context.checkPackageAccess(clazz.getName()); 1.73 } 1.74 1.75 - final ScriptObject realSelf; 1.76 - final ScriptObject ctxtGlobal = getNashornGlobalFrom(context); 1.77 - if(self == null) { 1.78 - realSelf = ctxtGlobal; 1.79 - } else if (!(self instanceof ScriptObject)) { 1.80 - realSelf = (ScriptObject)ScriptObjectMirror.unwrap(self, ctxtGlobal); 1.81 - } else { 1.82 - realSelf = (ScriptObject)self; 1.83 + ScriptObject realSelf = null; 1.84 + ScriptObject realGlobal = null; 1.85 + if(thiz == null) { 1.86 + // making interface out of global functions 1.87 + realSelf = realGlobal = getNashornGlobalFrom(context); 1.88 + } else if (thiz instanceof ScriptObjectMirror) { 1.89 + final ScriptObjectMirror mirror = (ScriptObjectMirror)thiz; 1.90 + realSelf = mirror.getScriptObject(); 1.91 + realGlobal = mirror.getHomeGlobal(); 1.92 + if (! realGlobal.isOfContext(nashornContext)) { 1.93 + throw new IllegalArgumentException(getMessage("script.object.from.another.engine")); 1.94 + } 1.95 + } else if (thiz instanceof ScriptObject) { 1.96 + // called from script code. 1.97 + realSelf = (ScriptObject)thiz; 1.98 + realGlobal = Context.getGlobal(); 1.99 + if (realGlobal == null) { 1.100 + throw new IllegalArgumentException(getMessage("no.current.nashorn.global")); 1.101 + } 1.102 + 1.103 + if (! realGlobal.isOfContext(nashornContext)) { 1.104 + throw new IllegalArgumentException(getMessage("script.object.from.another.engine")); 1.105 + } 1.106 + } 1.107 + 1.108 + if (realSelf == null) { 1.109 + throw new IllegalArgumentException(getMessage("interface.on.non.script.object")); 1.110 } 1.111 1.112 try { 1.113 final ScriptObject oldGlobal = Context.getGlobal(); 1.114 + final boolean globalChanged = (oldGlobal != realGlobal); 1.115 try { 1.116 - if(oldGlobal != ctxtGlobal) { 1.117 - Context.setGlobal(ctxtGlobal); 1.118 + if (globalChanged) { 1.119 + Context.setGlobal(realGlobal); 1.120 } 1.121 1.122 if (! isInterfaceImplemented(clazz, realSelf)) { 1.123 @@ -220,7 +265,7 @@ 1.124 } 1.125 return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz).invoke(realSelf)); 1.126 } finally { 1.127 - if(oldGlobal != ctxtGlobal) { 1.128 + if (globalChanged) { 1.129 Context.setGlobal(oldGlobal); 1.130 } 1.131 } 1.132 @@ -237,11 +282,11 @@ 1.133 } 1.134 1.135 @Override 1.136 - public <T> T getInterface(final Object self, final Class<T> clazz) { 1.137 - if (self == null) { 1.138 - throw new IllegalArgumentException("script object can not be null"); 1.139 + public <T> T getInterface(final Object thiz, final Class<T> clazz) { 1.140 + if (thiz == null) { 1.141 + throw new IllegalArgumentException(getMessage("thiz.cannot.be.null")); 1.142 } 1.143 - return getInterfaceInner(self, clazz); 1.144 + return getInterfaceInner(thiz, clazz); 1.145 } 1.146 1.147 // These are called from the "engine.js" script 1.148 @@ -362,13 +407,22 @@ 1.149 ScriptObjectMirror selfMirror = null; 1.150 if (selfObject instanceof ScriptObjectMirror) { 1.151 selfMirror = (ScriptObjectMirror)selfObject; 1.152 + if (! selfMirror.getHomeGlobal().isOfContext(nashornContext)) { 1.153 + throw new IllegalArgumentException(getMessage("script.object.from.another.engine")); 1.154 + } 1.155 } else if (selfObject instanceof ScriptObject) { 1.156 // invokeMethod called from script code - in which case we may get 'naked' ScriptObject 1.157 // Wrap it with oldGlobal to make a ScriptObjectMirror for the same. 1.158 final ScriptObject oldGlobal = Context.getGlobal(); 1.159 - if (oldGlobal != null) { 1.160 - selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(selfObject, oldGlobal); 1.161 + if (oldGlobal == null) { 1.162 + throw new IllegalArgumentException(getMessage("no.current.nashorn.global")); 1.163 } 1.164 + 1.165 + if (! oldGlobal.isOfContext(nashornContext)) { 1.166 + throw new IllegalArgumentException(getMessage("script.object.from.another.engine")); 1.167 + } 1.168 + 1.169 + selfMirror = (ScriptObjectMirror)ScriptObjectMirror.wrap(selfObject, oldGlobal); 1.170 } else if (selfObject == null) { 1.171 // selfObject is null => global function call 1.172 final ScriptObject ctxtGlobal = getNashornGlobalFrom(context); 1.173 @@ -389,7 +443,7 @@ 1.174 } 1.175 1.176 // Non-script object passed as selfObject 1.177 - throw new IllegalArgumentException("can not call invokeMethod on non-script objects"); 1.178 + throw new IllegalArgumentException(getMessage("interface.on.non.script.object")); 1.179 } 1.180 1.181 private Object evalImpl(final char[] buf, final ScriptContext ctxt) throws ScriptException {
2.1 --- a/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java Thu Jul 25 11:56:12 2013 +0200 2.2 +++ b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java Thu Jul 25 20:10:48 2013 +0530 2.3 @@ -599,14 +599,22 @@ 2.4 // package-privates below this. 2.5 2.6 ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) { 2.7 + assert sobj != null : "ScriptObjectMirror on null!"; 2.8 + assert global != null : "null global for ScriptObjectMirror!"; 2.9 + 2.10 this.sobj = sobj; 2.11 this.global = global; 2.12 } 2.13 2.14 + // accessors for script engine 2.15 ScriptObject getScriptObject() { 2.16 return sobj; 2.17 } 2.18 2.19 + ScriptObject getHomeGlobal() { 2.20 + return global; 2.21 + } 2.22 + 2.23 static Object translateUndefined(Object obj) { 2.24 return (obj == ScriptRuntime.UNDEFINED)? null : obj; 2.25 }
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/src/jdk/nashorn/api/scripting/resources/Messages.properties Thu Jul 25 20:10:48 2013 +0530 3.3 @@ -0,0 +1,32 @@ 3.4 +# 3.5 +# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3.6 +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3.7 +# 3.8 +# This code is free software; you can redistribute it and/or modify it 3.9 +# under the terms of the GNU General Public License version 2 only, as 3.10 +# published by the Free Software Foundation. Oracle designates this 3.11 +# particular file as subject to the "Classpath" exception as provided 3.12 +# by Oracle in the LICENSE file that accompanied this code. 3.13 +# 3.14 +# This code is distributed in the hope that it will be useful, but WITHOUT 3.15 +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 3.16 +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 3.17 +# version 2 for more details (a copy is included in the LICENSE file that 3.18 +# accompanied this code). 3.19 +# 3.20 +# You should have received a copy of the GNU General Public License version 3.21 +# 2 along with this work; if not, write to the Free Software Foundation, 3.22 +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 3.23 +# 3.24 +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 3.25 +# or visit www.oracle.com if you need additional information or have any 3.26 +# questions. 3.27 +# 3.28 + 3.29 +thiz.cannot.be.null=script object 'this' for getMethod, getInterface calls can not be null 3.30 +interface.class.expected=interface Class expected in getInterface 3.31 +interface.on.non.script.object=getInterface cannot be called on non-script object 3.32 +no.current.nashorn.global=no current Global instance for nashorn 3.33 +implementing.non.public.interface=Cannot implement non-public interface: {0} 3.34 +script.object.from.another.engine=Script object belongs to another script engine 3.35 +
4.1 --- a/src/jdk/nashorn/internal/runtime/ScriptObject.java Thu Jul 25 11:56:12 2013 +0200 4.2 +++ b/src/jdk/nashorn/internal/runtime/ScriptObject.java Thu Jul 25 20:10:48 2013 +0530 4.3 @@ -1046,6 +1046,15 @@ 4.4 } 4.5 4.6 /** 4.7 + * Checks if this object belongs to the given context 4.8 + * @param ctx context to check against 4.9 + * @return true if this object belongs to the given context 4.10 + */ 4.11 + public final boolean isOfContext(final Context ctx) { 4.12 + return context == ctx; 4.13 + } 4.14 + 4.15 + /** 4.16 * Return the current context from the object's map. 4.17 * @return Current context. 4.18 */
5.1 --- a/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java Thu Jul 25 11:56:12 2013 +0200 5.2 +++ b/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java Thu Jul 25 20:10:48 2013 +0530 5.3 @@ -363,6 +363,90 @@ 5.4 } 5.5 5.6 @Test 5.7 + /** 5.8 + * Check that we can get interface out of a script object even after 5.9 + * switching to use different ScriptContext. 5.10 + */ 5.11 + public void getInterfaceDifferentContext() { 5.12 + ScriptEngineManager m = new ScriptEngineManager(); 5.13 + ScriptEngine e = m.getEngineByName("nashorn"); 5.14 + try { 5.15 + Object obj = e.eval("({ run: function() { } })"); 5.16 + 5.17 + // change script context 5.18 + ScriptContext ctxt = new SimpleScriptContext(); 5.19 + ctxt.setBindings(e.createBindings(), ScriptContext.ENGINE_SCOPE); 5.20 + e.setContext(ctxt); 5.21 + 5.22 + Runnable r = ((Invocable)e).getInterface(obj, Runnable.class); 5.23 + r.run(); 5.24 + }catch (final Exception exp) { 5.25 + exp.printStackTrace(); 5.26 + fail(exp.getMessage()); 5.27 + } 5.28 + } 5.29 + 5.30 + @Test 5.31 + /** 5.32 + * Check that getInterface on non-script object 'thiz' results in IllegalArgumentException. 5.33 + */ 5.34 + public void getInterfaceNonScriptObjectThizTest() { 5.35 + final ScriptEngineManager m = new ScriptEngineManager(); 5.36 + final ScriptEngine e = m.getEngineByName("nashorn"); 5.37 + 5.38 + try { 5.39 + ((Invocable)e).getInterface(new Object(), Runnable.class); 5.40 + fail("should have thrown IllegalArgumentException"); 5.41 + } catch (final Exception exp) { 5.42 + if (! (exp instanceof IllegalArgumentException)) { 5.43 + exp.printStackTrace(); 5.44 + fail(exp.getMessage()); 5.45 + } 5.46 + } 5.47 + } 5.48 + 5.49 + @Test 5.50 + /** 5.51 + * Check that getInterface on null 'thiz' results in IllegalArgumentException. 5.52 + */ 5.53 + public void getInterfaceNullThizTest() { 5.54 + final ScriptEngineManager m = new ScriptEngineManager(); 5.55 + final ScriptEngine e = m.getEngineByName("nashorn"); 5.56 + 5.57 + try { 5.58 + ((Invocable)e).getInterface(null, Runnable.class); 5.59 + fail("should have thrown IllegalArgumentException"); 5.60 + } catch (final Exception exp) { 5.61 + if (! (exp instanceof IllegalArgumentException)) { 5.62 + exp.printStackTrace(); 5.63 + fail(exp.getMessage()); 5.64 + } 5.65 + } 5.66 + } 5.67 + 5.68 + @Test 5.69 + /** 5.70 + * Check that calling getInterface on mirror created by another engine results in IllegalArgumentException. 5.71 + */ 5.72 + public void getInterfaceMixEnginesTest() { 5.73 + final ScriptEngineManager m = new ScriptEngineManager(); 5.74 + final ScriptEngine engine1 = m.getEngineByName("nashorn"); 5.75 + final ScriptEngine engine2 = m.getEngineByName("nashorn"); 5.76 + 5.77 + try { 5.78 + Object obj = engine1.eval("({ run: function() {} })"); 5.79 + // pass object from engine1 to engine2 as 'thiz' for getInterface 5.80 + ((Invocable)engine2).getInterface(obj, Runnable.class); 5.81 + fail("should have thrown IllegalArgumentException"); 5.82 + } catch (final Exception exp) { 5.83 + if (! (exp instanceof IllegalArgumentException)) { 5.84 + exp.printStackTrace(); 5.85 + fail(exp.getMessage()); 5.86 + } 5.87 + } 5.88 + } 5.89 + 5.90 + @Test 5.91 public void accessGlobalTest() { 5.92 final ScriptEngineManager m = new ScriptEngineManager(); 5.93 final ScriptEngine e = m.getEngineByName("nashorn"); 5.94 @@ -734,6 +818,48 @@ 5.95 } 5.96 5.97 @Test 5.98 + /** 5.99 + * Check that calling method on null 'thiz' results in IllegalArgumentException. 5.100 + */ 5.101 + public void invokeMethodNullThizTest() { 5.102 + final ScriptEngineManager m = new ScriptEngineManager(); 5.103 + final ScriptEngine e = m.getEngineByName("nashorn"); 5.104 + 5.105 + try { 5.106 + ((Invocable)e).invokeMethod(null, "toString"); 5.107 + fail("should have thrown IllegalArgumentException"); 5.108 + } catch (final Exception exp) { 5.109 + if (! (exp instanceof IllegalArgumentException)) { 5.110 + exp.printStackTrace(); 5.111 + fail(exp.getMessage()); 5.112 + } 5.113 + } 5.114 + } 5.115 + 5.116 + 5.117 + @Test 5.118 + /** 5.119 + * Check that calling method on mirror created by another engine results in IllegalArgumentException. 5.120 + */ 5.121 + public void invokeMethodMixEnginesTest() { 5.122 + final ScriptEngineManager m = new ScriptEngineManager(); 5.123 + final ScriptEngine engine1 = m.getEngineByName("nashorn"); 5.124 + final ScriptEngine engine2 = m.getEngineByName("nashorn"); 5.125 + 5.126 + try { 5.127 + Object obj = engine1.eval("({ run: function() {} })"); 5.128 + // pass object from engine1 to engine2 as 'thiz' for invokeMethod 5.129 + ((Invocable)engine2).invokeMethod(obj, "run"); 5.130 + fail("should have thrown IllegalArgumentException"); 5.131 + } catch (final Exception exp) { 5.132 + if (! (exp instanceof IllegalArgumentException)) { 5.133 + exp.printStackTrace(); 5.134 + fail(exp.getMessage()); 5.135 + } 5.136 + } 5.137 + } 5.138 + 5.139 + @Test 5.140 public void noEnumerablePropertiesTest() { 5.141 final ScriptEngineManager m = new ScriptEngineManager(); 5.142 final ScriptEngine e = m.getEngineByName("nashorn");