8060483: NPE with explicitCastArguments unboxing null
authorvlivanov
Fri, 24 Oct 2014 08:22:17 -0700
changeset 27196 fe9dbab34d1d
parent 27195 b33768211561
child 27197 0f0c55288d35
8060483: NPE with explicitCastArguments unboxing null Reviewed-by: attila, lagergren
jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java
jdk/src/java.base/share/classes/sun/invoke/util/Wrapper.java
jdk/test/java/lang/invoke/ExplicitCastArgumentsTest.java
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java	Fri Oct 24 06:18:00 2014 -0700
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java	Fri Oct 24 08:22:17 2014 -0700
@@ -889,11 +889,6 @@
      *     with MHs.eCE.
      *  3a. unboxing conversions can be followed by the full matrix of primitive conversions
      *  3b. unboxing of null is permitted (creates a zero primitive value)
-     *     Most unboxing conversions, like {@code Object->int}, has potentially
-     *     different behaviors for asType vs. MHs.eCE, because the dynamic value
-     *     might be a wrapper of a type that requires narrowing, like {@code (Object)1L->byte}.
-     *     The equivalence is only certain if the static src type is a wrapper,
-     *     and the conversion will be a widening one.
      * Other than interfaces, reference-to-reference conversions are the same.
      * Boxing primitives to references is the same for both operators.
      */
@@ -904,11 +899,8 @@
             // Or a boxing conversion, which is always to an exact wrapper class.
             return canConvert(src, dst);
         } else if (dst.isPrimitive()) {
-            Wrapper dw = Wrapper.forPrimitiveType(dst);
-            // Watch out:  If src is Number or Object, we could get dynamic narrowing conversion.
-            // The conversion is known to be widening only if the wrapper type is statically visible.
-            return (Wrapper.isWrapperType(src) &&
-                    dw.isConvertibleFrom(Wrapper.forWrapperType(src)));
+            // Unboxing behavior is different between MHs.eCA & MH.asType (see 3b).
+            return false;
         } else {
             // R->R always works, but we have to avoid a check-cast to an interface.
             return !dst.isInterface() || dst.isAssignableFrom(src);
--- a/jdk/src/java.base/share/classes/sun/invoke/util/Wrapper.java	Fri Oct 24 06:18:00 2014 -0700
+++ b/jdk/src/java.base/share/classes/sun/invoke/util/Wrapper.java	Fri Oct 24 08:22:17 2014 -0700
@@ -26,19 +26,19 @@
 package sun.invoke.util;
 
 public enum Wrapper {
-    BOOLEAN(Boolean.class, boolean.class, 'Z', (Boolean)false, new boolean[0], Format.unsigned(1)),
+    //        wrapperType    primitiveType  char            zero         emptyArray          format
+    BOOLEAN(  Boolean.class, boolean.class, 'Z',      (Boolean)false, new boolean[0], Format.unsigned( 1)),
     // These must be in the order defined for widening primitive conversions in JLS 5.1.2
-    BYTE(Byte.class, byte.class, 'B', (Byte)(byte)0, new byte[0], Format.signed(8)),
-    SHORT(Short.class, short.class, 'S', (Short)(short)0, new short[0], Format.signed(16)),
-    CHAR(Character.class, char.class, 'C', (Character)(char)0, new char[0], Format.unsigned(16)),
-    INT(Integer.class, int.class, 'I', (Integer)/*(int)*/0, new int[0], Format.signed(32)),
-    LONG(Long.class, long.class, 'J', (Long)(long)0, new long[0], Format.signed(64)),
-    FLOAT(Float.class, float.class, 'F', (Float)(float)0, new float[0], Format.floating(32)),
-    DOUBLE(Double.class, double.class, 'D', (Double)(double)0, new double[0], Format.floating(64)),
-    //NULL(Null.class, null.class, 'N', null, null, Format.other(1)),
-    OBJECT(Object.class, Object.class, 'L', null, new Object[0], Format.other(1)),
+    BYTE   (     Byte.class,    byte.class, 'B',       (Byte)(byte)0, new    byte[0], Format.signed(   8)),
+    SHORT  (    Short.class,   short.class, 'S',     (Short)(short)0, new   short[0], Format.signed(  16)),
+    CHAR   (Character.class,    char.class, 'C',  (Character)(char)0, new    char[0], Format.unsigned(16)),
+    INT    (  Integer.class,     int.class, 'I', (Integer)/*(int)*/0, new     int[0], Format.signed(  32)),
+    LONG   (     Long.class,    long.class, 'J',       (Long)(long)0, new    long[0], Format.signed(  64)),
+    FLOAT  (    Float.class,   float.class, 'F',     (Float)(float)0, new   float[0], Format.floating(32)),
+    DOUBLE (   Double.class,  double.class, 'D',   (Double)(double)0, new  double[0], Format.floating(64)),
+    OBJECT (   Object.class,  Object.class, 'L',                null, new  Object[0], Format.other(    1)),
     // VOID must be the last type, since it is "assignable" from any other type:
-    VOID(Void.class, void.class, 'V', null, null, Format.other(0)),
+    VOID   (     Void.class,    void.class, 'V',                null,           null, Format.other(    0)),
     ;
 
     private final Class<?> wrapperType;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/ExplicitCastArgumentsTest.java	Fri Oct 24 08:22:17 2014 -0700
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+package java.lang.invoke;
+
+import sun.invoke.util.Wrapper;
+
+/* @test
+ * @summary unit tests for MethodHandles.explicitCastArguments()
+ *
+ * @run main/bootclasspath java.lang.invoke.ExplicitCastArgumentsTest
+ */
+public class ExplicitCastArgumentsTest {
+    private static final boolean VERBOSE = Boolean.getBoolean("verbose");
+
+    public static void main(String[] args) throws Throwable {
+        for (Wrapper from : Wrapper.values()) {
+            for (Wrapper to : Wrapper.values()) {
+                if (from == Wrapper.VOID || to == Wrapper.VOID) continue;
+                testRef2Prim (from, to);
+            }
+        }
+        System.out.println("TEST PASSED");
+    }
+
+    public static void testRef2Prim(Wrapper from, Wrapper to) throws Throwable {
+        // MHs.eCA javadoc:
+        //    If T0 is a reference and T1 a primitive, and if the reference is null at runtime, a zero value is introduced.
+        test(from.wrapperType(), to.primitiveType(),        null, false);
+    }
+
+    public static void test(Class<?> from, Class<?> to, Object param, boolean failureExpected) throws Throwable {
+        if (VERBOSE) System.out.printf("%-10s => %-10s: %5s: ", from.getSimpleName(), to.getSimpleName(), param);
+
+        MethodHandle original = MethodHandles.identity(from);
+        MethodType newType = original.type().changeReturnType(to);
+
+        try {
+            MethodHandle target = MethodHandles.explicitCastArguments(original, newType);
+            Object result = target.invokeWithArguments(param);
+
+            if (VERBOSE) {
+                String resultStr;
+                if (result != null) {
+                    resultStr = String.format("%10s (%10s)", "'"+result+"'", result.getClass().getSimpleName());
+                } else {
+                    resultStr = String.format("%10s", result);
+                }
+                System.out.println(resultStr);
+            }
+
+            if (failureExpected) {
+                String msg = String.format("No exception thrown: %s => %s; parameter: %s", from, to, param);
+                throw new AssertionError(msg);
+            }
+        } catch (AssertionError e) {
+            throw e; // report test failure
+        } catch (Throwable e) {
+            if (VERBOSE) System.out.printf("%s: %s\n", e.getClass(), e.getMessage());
+            if (!failureExpected) {
+                String msg = String.format("Unexpected exception was thrown: %s => %s; parameter: %s", from, to, param);
+                throw new AssertionError(msg, e);
+            }
+        }
+    }
+}