From 0c95b0fb5f6b01c10e1ed1facbfcfe7c3cff2be3 Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Fri, 24 Oct 2014 08:22:17 -0700 Subject: [PATCH] 8060483: NPE with explicitCastArguments unboxing null Reviewed-by: attila, lagergren --- .../classes/java/lang/invoke/MethodType.java | 12 +-- .../classes/sun/invoke/util/Wrapper.java | 22 ++--- .../invoke/ExplicitCastArgumentsTest.java | 86 +++++++++++++++++++ 3 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 jdk/test/java/lang/invoke/ExplicitCastArgumentsTest.java diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java index 00e0a046efe..b15c188d72b 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java @@ -889,11 +889,6 @@ class MethodType implements java.io.Serializable { * 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 @@ class MethodType implements java.io.Serializable { // 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); diff --git a/jdk/src/java.base/share/classes/sun/invoke/util/Wrapper.java b/jdk/src/java.base/share/classes/sun/invoke/util/Wrapper.java index 2732e28c8ed..0a0d04b3c74 100644 --- a/jdk/src/java.base/share/classes/sun/invoke/util/Wrapper.java +++ b/jdk/src/java.base/share/classes/sun/invoke/util/Wrapper.java @@ -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; diff --git a/jdk/test/java/lang/invoke/ExplicitCastArgumentsTest.java b/jdk/test/java/lang/invoke/ExplicitCastArgumentsTest.java new file mode 100644 index 00000000000..06f7a64aabd --- /dev/null +++ b/jdk/test/java/lang/invoke/ExplicitCastArgumentsTest.java @@ -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); + } + } + } +}