8027828: ClassCastException when converting return value of a Java method to boolean
authorsundar
Thu, 07 Nov 2013 17:26:46 +0530
changeset 21691 8e284e9a6144
parent 21688 90bb8dc029c7
child 21692 e1c40a93dd66
child 21694 e15e1f8befa0
8027828: ClassCastException when converting return value of a Java method to boolean Reviewed-by: jlaskey, attila
nashorn/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java
nashorn/src/jdk/nashorn/api/scripting/ScriptUtils.java
nashorn/src/jdk/nashorn/internal/runtime/JSType.java
nashorn/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java
nashorn/test/script/basic/JDK-8027828.js
nashorn/test/script/basic/JDK-8027828.js.EXPECTED
nashorn/test/script/basic/convert.js
nashorn/test/script/basic/convert.js.EXPECTED
nashorn/test/src/jdk/nashorn/api/scripting/ScriptObjectMirrorTest.java
--- a/nashorn/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Mon Nov 04 18:52:22 2013 +0530
+++ b/nashorn/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Thu Nov 07 17:26:46 2013 +0530
@@ -595,6 +595,21 @@
     }
 
     /**
+     * Utilitity to convert this script object to the given type.
+     *
+     * @param type destination type to convert to
+     * @return converted object
+     */
+    public <T> T to(final Class<T> type) {
+        return inGlobal(new Callable<T>() {
+            @Override
+            public T call() {
+                return type.cast(ScriptUtils.convert(sobj, type));
+            }
+        });
+    }
+
+    /**
      * Make a script object mirror on given object if needed. Also converts ConsString instances to Strings.
      *
      * @param obj object to be wrapped/converted
--- a/nashorn/src/jdk/nashorn/api/scripting/ScriptUtils.java	Mon Nov 04 18:52:22 2013 +0530
+++ b/nashorn/src/jdk/nashorn/api/scripting/ScriptUtils.java	Thu Nov 07 17:26:46 2013 +0530
@@ -25,13 +25,17 @@
 
 package jdk.nashorn.api.scripting;
 
+import java.lang.invoke.MethodHandle;
+import jdk.internal.dynalink.beans.StaticClass;
+import jdk.internal.dynalink.linker.LinkerServices;
+import jdk.nashorn.internal.runtime.linker.Bootstrap;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
 
 /**
- * Utilities that are to be called from script code
+ * Utilities that are to be called from script code.
  */
 public final class ScriptUtils {
     private ScriptUtils() {}
@@ -128,4 +132,41 @@
 
         return ScriptObjectMirror.unwrapArray(args, Context.getGlobal());
     }
+
+    /**
+     * Convert the given object to the given type.
+     *
+     * @param obj object to be converted
+     * @param type destination type to convert to
+     * @return converted object
+     */
+    public static Object convert(final Object obj, final Object type) {
+        if (obj == null) {
+            return null;
+        }
+
+        final Class<?> clazz;
+        if (type instanceof Class) {
+            clazz = (Class<?>)type;
+        } else if (type instanceof StaticClass) {
+            clazz = ((StaticClass)type).getRepresentedClass();
+        } else {
+            throw new IllegalArgumentException("type expected");
+        }
+
+        final LinkerServices linker = Bootstrap.getLinkerServices();
+        final MethodHandle converter = linker.getTypeConverter(obj.getClass(),  clazz);
+        if (converter == null) {
+            // no supported conversion!
+            throw new UnsupportedOperationException("conversion not supported");
+        }
+
+        try {
+            return converter.invoke(obj);
+        } catch (final RuntimeException | Error e) {
+            throw e;
+        } catch (final Throwable t) {
+            throw new RuntimeException(t);
+        }
+    }
 }
--- a/nashorn/src/jdk/nashorn/internal/runtime/JSType.java	Mon Nov 04 18:52:22 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/JSType.java	Thu Nov 07 17:26:46 2013 +0530
@@ -88,6 +88,9 @@
     /** JavaScript compliant conversion function from Object to number */
     public static final Call TO_NUMBER = staticCall(myLookup, JSType.class, "toNumber", double.class, Object.class);
 
+    /** JavaScript compliant conversion function from Object to String */
+    public static final Call TO_STRING = staticCall(myLookup, JSType.class, "toString", String.class, Object.class);
+
     /** JavaScript compliant conversion function from Object to int32 */
     public static final Call TO_INT32 = staticCall(myLookup, JSType.class, "toInt32", int.class, Object.class);
 
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java	Mon Nov 04 18:52:22 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java	Thu Nov 07 17:26:46 2013 +0530
@@ -33,14 +33,18 @@
 import java.lang.invoke.MethodType;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.util.Map;
+import java.util.HashMap;
 import jdk.internal.dynalink.CallSiteDescriptor;
 import jdk.internal.dynalink.beans.BeansLinker;
 import jdk.internal.dynalink.linker.GuardedInvocation;
 import jdk.internal.dynalink.linker.GuardingDynamicLinker;
+import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
 import jdk.internal.dynalink.linker.LinkRequest;
 import jdk.internal.dynalink.linker.LinkerServices;
 import jdk.internal.dynalink.support.Guards;
 import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.JSType;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
 
 /**
@@ -50,7 +54,7 @@
  * setters for Java objects that couldn't be linked by any other linker, and throw appropriate ECMAScript errors for
  * attempts to invoke arbitrary Java objects as functions or constructors.
  */
-final class NashornBottomLinker implements GuardingDynamicLinker {
+final class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeConverterFactory {
 
     @Override
     public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices)
@@ -129,6 +133,29 @@
         throw new AssertionError("unknown call type " + desc);
     }
 
+    @Override
+    public GuardedInvocation convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
+        final GuardedInvocation gi = convertToTypeNoCast(sourceType, targetType);
+        return gi == null ? null : gi.asType(MH.type(targetType, sourceType));
+    }
+
+    /**
+     * Main part of the implementation of {@link GuardingTypeConverterFactory#convertToType(Class, Class)} that doesn't
+     * care about adapting the method signature; that's done by the invoking method. Returns conversion from Object to String/number/boolean (JS primitive types).
+     * @param sourceType the source type
+     * @param targetType the target type
+     * @return a guarded invocation that converts from the source type to the target type.
+     * @throws Exception if something goes wrong
+     */
+    private static GuardedInvocation convertToTypeNoCast(final Class<?> sourceType, final Class<?> targetType) throws Exception {
+        final MethodHandle mh = CONVERTERS.get(targetType);
+        if (mh != null) {
+            return new GuardedInvocation(mh, null);
+        }
+
+        return null;
+    }
+
     private static GuardedInvocation getInvocation(final MethodHandle handle, final Object self, final LinkerServices linkerServices, final CallSiteDescriptor desc) {
         return Bootstrap.asType(new GuardedInvocation(handle, Guards.getClassGuard(self.getClass())), linkerServices, desc);
     }
@@ -161,6 +188,15 @@
         throw new AssertionError("unknown call type " + desc);
     }
 
+    private static final Map<Class<?>, MethodHandle> CONVERTERS = new HashMap<>();
+    static {
+        CONVERTERS.put(boolean.class, JSType.TO_BOOLEAN.methodHandle());
+        CONVERTERS.put(double.class, JSType.TO_NUMBER.methodHandle());
+        CONVERTERS.put(int.class, JSType.TO_INTEGER.methodHandle());
+        CONVERTERS.put(long.class, JSType.TO_LONG.methodHandle());
+        CONVERTERS.put(String.class, JSType.TO_STRING.methodHandle());
+    }
+
     private static String getArgument(final LinkRequest linkRequest) {
         final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor();
         if (desc.getNameTokenCount() > 2) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8027828.js	Thu Nov 07 17:26:46 2013 +0530
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ * 
+ * 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.
+ */
+
+/**
+ * JDK-8027828: ClassCastException when converting return value of a Java method to boolean
+ *
+ * @test
+ * @run
+ */
+
+var x = new java.util.HashMap()
+x.put('test', new java.io.File('test'))
+if (x.get("test")) {
+  print('Found!')
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8027828.js.EXPECTED	Thu Nov 07 17:26:46 2013 +0530
@@ -0,0 +1,1 @@
+Found!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/convert.js	Thu Nov 07 17:26:46 2013 +0530
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ * 
+ * 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.
+ */
+
+/**
+ * Tests for convert method of ScriptUtils.
+ *
+ * @test
+ * @run
+ */
+
+var ScriptUtils = Java.type("jdk.nashorn.api.scripting.ScriptUtils");
+obj = { valueOf: function() { print("hello"); return 43.3; } };
+
+// object to double
+print(ScriptUtils.convert(obj, java.lang.Number.class));
+
+// array to List
+var arr = [3, 44, 23, 33];
+var list = ScriptUtils.convert(arr, java.util.List.class);
+print(list instanceof java.util.List)
+print(list);
+
+// object to Map
+obj = { foo: 333, bar: 'hello'};
+var map = ScriptUtils.convert(obj, java.util.Map.class);
+print(map instanceof java.util.Map);
+for (m in map) {
+   print(m + " " + map[m]);
+}
+
+// object to String
+obj = { toString: function() { print("in toString"); return "foo" } };
+print(ScriptUtils.convert(obj, java.lang.String.class));
+
+// array to Java array
+var jarr = ScriptUtils.convert(arr, Java.type("int[]"));
+print(jarr instanceof Java.type("int[]"));
+for (i in jarr) {
+    print(jarr[i]);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/convert.js.EXPECTED	Thu Nov 07 17:26:46 2013 +0530
@@ -0,0 +1,14 @@
+hello
+43.3
+true
+[3, 44, 23, 33]
+true
+foo 333
+bar hello
+in toString
+foo
+true
+3
+44
+23
+33
--- a/nashorn/test/src/jdk/nashorn/api/scripting/ScriptObjectMirrorTest.java	Mon Nov 04 18:52:22 2013 +0530
+++ b/nashorn/test/src/jdk/nashorn/api/scripting/ScriptObjectMirrorTest.java	Thu Nov 07 17:26:46 2013 +0530
@@ -26,6 +26,7 @@
 package jdk.nashorn.api.scripting;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineManager;
@@ -227,4 +228,28 @@
         final Object newObj = ((ScriptObjectMirror)e2obj.getMember("foo")).newObject();
         assertTrue(newObj instanceof ScriptObjectMirror);
     }
+
+    @Test
+    public void conversionTest() throws ScriptException {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        final ScriptObjectMirror arr = (ScriptObjectMirror)e.eval("[33, 45, 23]");
+        final int[] intArr = arr.to(int[].class);
+        assertEquals(intArr[0], 33);
+        assertEquals(intArr[1], 45);
+        assertEquals(intArr[2], 23);
+
+        final List<?> list = arr.to(List.class);
+        assertEquals(list.get(0), 33);
+        assertEquals(list.get(1), 45);
+        assertEquals(list.get(2), 23);
+
+        ScriptObjectMirror obj = (ScriptObjectMirror)e.eval(
+            "({ valueOf: function() { return 42 } })");
+        assertEquals(Double.valueOf(42.0), obj.to(Double.class));
+
+        obj = (ScriptObjectMirror)e.eval(
+            "({ toString: function() { return 'foo' } })");
+        assertEquals("foo", obj.to(String.class));
+    }
 }