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

Thu, 25 Jul 2013 20:10:48 +0530

author
sundar
date
Thu, 25 Jul 2013 20:10:48 +0530
changeset 472
f22ca0f9b6ee
parent 471
f74faac51bfb
child 473
d55856f82352
child 474
f6588f168d79

8021361: ClassCastException:.ScriptObjectMirror -> ScriptObject when getInterface called on object from different ScriptContext
Reviewed-by: jlaskey, attila

src/jdk/nashorn/api/scripting/NashornScriptEngine.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/api/scripting/ScriptObjectMirror.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/api/scripting/resources/Messages.properties file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/ScriptObject.java file | annotate | diff | comparison | revisions
test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java file | annotate | diff | comparison | revisions
     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");

mercurial