diff --git a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java index b1282970ccf6..37f9aa90c883 100644 --- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -281,9 +281,9 @@ void validateMetafactoryArgs() throws LambdaConversionException { for (int i=capturedStart; i implParamType = implMethodType.parameterType(i); Class capturedParamType = factoryType.parameterType(i); - if (!capturedParamType.equals(implParamType)) { + if (!MethodType.canConvert(capturedParamType, implParamType)) { throw new LambdaConversionException( - String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s", + String.format("Type mismatch in captured lambda parameter %d: %s is not convertible to %s", i, capturedParamType, implParamType)); } } @@ -291,7 +291,7 @@ void validateMetafactoryArgs() throws LambdaConversionException { for (int i=samStart; i implParamType = implMethodType.parameterType(i); Class dynamicParamType = dynamicMethodType.parameterType(i - capturedArity); - if (!isAdaptableTo(dynamicParamType, implParamType, true)) { + if (!MethodType.canConvert(dynamicParamType, implParamType)) { throw new LambdaConversionException( String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", i, dynamicParamType, implParamType)); @@ -301,7 +301,7 @@ void validateMetafactoryArgs() throws LambdaConversionException { // Adaptation match: return type Class expectedType = dynamicMethodType.returnType(); Class actualReturnType = implMethodType.returnType(); - if (!isAdaptableToAsReturn(actualReturnType, expectedType)) { + if (!MethodType.canConvert(actualReturnType, expectedType)) { throw new LambdaConversionException( String.format("Type mismatch for lambda return: %s is not convertible to %s", actualReturnType, expectedType)); @@ -319,7 +319,7 @@ private void checkDescriptor(MethodType descriptor) throws LambdaConversionExcep for (int i = 0; i < dynamicMethodType.parameterCount(); i++) { Class dynamicParamType = dynamicMethodType.parameterType(i); Class descriptorParamType = descriptor.parameterType(i); - if (!descriptorParamType.isAssignableFrom(dynamicParamType)) { + if (!MethodType.canConvert(dynamicParamType, descriptorParamType)) { String msg = String.format("Type mismatch for dynamic parameter %d: %s is not a subtype of %s", i, dynamicParamType, descriptorParamType); throw new LambdaConversionException(msg); @@ -328,7 +328,7 @@ private void checkDescriptor(MethodType descriptor) throws LambdaConversionExcep Class dynamicReturnType = dynamicMethodType.returnType(); Class descriptorReturnType = descriptor.returnType(); - if (!isAdaptableToAsReturnStrict(dynamicReturnType, descriptorReturnType)) { + if (!MethodType.canConvert(dynamicReturnType, descriptorReturnType)) { String msg = String.format("Type mismatch for lambda expected return: %s is not convertible to %s", dynamicReturnType, descriptorReturnType); throw new LambdaConversionException(msg); diff --git a/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java b/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java index a22e5dac2df7..29295cc01dfe 100644 --- a/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java +++ b/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java @@ -23,10 +23,16 @@ /* * @test - * @bug 8035776 8173587 + * @bug 8035776 8173587 8269121 * @summary metafactory should fail if instantiatedMethodType does not match sam/bridge descriptors + * @modules java.base/sun.invoke.util */ + +import sun.invoke.util.Wrapper; + import java.lang.invoke.*; +import java.lang.reflect.Modifier; + import java.util.*; public class MetafactoryDescriptorTest { @@ -37,8 +43,6 @@ static MethodType mt(Class ret, Class... params) { return MethodType.methodType(ret, params); } - public interface I {} - public static class C { public static void m_void(String arg) {} public static boolean m_boolean(String arg) { return true; } @@ -52,6 +56,10 @@ public static void m_void(String arg) {} public static String m_String(String arg) { return ""; } public static Integer m_Integer(String arg) { return 23; } public static Object m_Object(String arg) { return new Object(); } + public static I m_I(String arg) { return new I() {}; } + public static J m_J(String arg) { return new J() {}; } + public static CC m_CC(String arg) { return new CC(); } + public static FF m_FF(String arg) { return new FF(); } public static String n_boolean(boolean arg) { return ""; } public static String n_char(char arg) { return ""; } @@ -64,6 +72,10 @@ public static void m_void(String arg) {} public static String n_String(String arg) { return ""; } public static String n_Integer(Integer arg) { return ""; } public static String n_Object(Object arg) { return ""; } + public static String n_I(I arg) { return ""; } + public static String n_J(J arg) { return ""; } + public static String n_CC(CC arg) { return ""; } + public static String n_FF(FF arg) { return ""; } public static MethodHandle getM(Class c) { try { @@ -87,15 +99,18 @@ public static MethodHandle getN(Class c) { } public static void main(String... args) { - Class[] t = { void.class, boolean.class, char.class, + Class[] t = { void.class, boolean.class + /*, char.class, byte.class, short.class, int.class, long.class, float.class, double.class, - String.class, Integer.class, Object.class }; + String.class, Integer.class, Object.class, + I.class, J.class, CC.class, FF.class*/}; for (int i = 0; i < t.length; i++) { MethodHandle m = C.getM(t[i]); MethodHandle n = C.getN(t[i]); // null for void.class for (int j = 0; j < t.length; j++) { - boolean correctRet = t[j].isAssignableFrom(t[i]) || conversions.contains(t[i], t[j]); + //if (i == j) continue; + boolean correctRet = canConvert(t[i], t[j]) || conversions.contains(t[i], t[j]); test(correctRet, m, mt(t[i], String.class), mt(t[j], String.class)); testBridge(correctRet, m, mt(t[i], String.class), mt(t[i], String.class), mt(t[j], Object.class)); @@ -103,7 +118,9 @@ public static void main(String... args) { mt(t[i], CharSequence.class), mt(t[j], Object.class)); if (t[i] != void.class && t[j] != void.class) { - boolean correctParam = t[j].isAssignableFrom(t[i]); + //boolean correctParam = t[j].isAssignableFrom(t[i]) || sideCastExists(t[i], t[j]); + boolean correctParam = canConvert(t[i], t[j]); + System.out.println("testing correctParam = " + correctParam + " t[i] = " + t[i] + " t[j] = " + t[j]); test(correctParam, n, mt(String.class, t[i]), mt(String.class, t[j])); testBridge(correctParam, n, mt(String.class, t[i]), mt(String.class, t[i]), mt(Object.class, t[j])); @@ -126,6 +143,11 @@ static void testBridge(boolean correct, MethodHandle mh, MethodType instMT, Meth static void tryMetafactory(boolean correct, MethodHandle mh, MethodType instMT, MethodType samMT) { try { + System.err.println("invoking metafactory:" + + " factory = " + mt(I.class) + + " impl=" + mh + + ", inst=" + instMT + + ", sam=" + samMT); LambdaMetafactory.metafactory(lookup, "run", mt(I.class), samMT, mh, instMT); if (!correct) { @@ -266,4 +288,63 @@ public boolean contains(Class from, Class to) { conversions.put(Boolean.class, boolean.class); } + private static boolean canConvert(Class src, Class dst) { + // short-circuit a few cases: + if (src == dst || src == Object.class || dst == Object.class) return true; + // the remainder of this logic is documented in MethodHandle.asType + if (src.isPrimitive()) { + // can force void to an explicit null, a la reflect.Method.invoke + // can also force void to a primitive zero, by analogy + if (src == void.class) return true; //or !dst.isPrimitive()? + Wrapper sw = Wrapper.forPrimitiveType(src); + if (dst.isPrimitive()) { + // P->P must widen + return Wrapper.forPrimitiveType(dst).isConvertibleFrom(sw); + } else { + // P->R must box and widen + return dst.isAssignableFrom(sw.wrapperType()); + } + } else if (dst.isPrimitive()) { + // any value can be dropped + if (dst == void.class) return true; + Wrapper dw = Wrapper.forPrimitiveType(dst); + // R->P must be able to unbox (from a dynamically chosen type) and widen + // For example: + // Byte/Number/Comparable/Object -> dw:Byte -> byte. + // Character/Comparable/Object -> dw:Character -> char + // Boolean/Comparable/Object -> dw:Boolean -> boolean + // This means that dw must be cast-compatible with src. + if (src.isAssignableFrom(dw.wrapperType())) { + return true; + } + // The above does not work if the source reference is strongly typed + // to a wrapper whose primitive must be widened. For example: + // Byte -> unbox:byte -> short/int/long/float/double + // Character -> unbox:char -> int/long/float/double + if (Wrapper.isWrapperType(src) && + dw.isConvertibleFrom(Wrapper.forWrapperType(src))) { + // can unbox from src and then widen to dst + return true; + } + // We have already covered cases which arise due to runtime unboxing + // of a reference type which covers several wrapper types: + // Object -> cast:Integer -> unbox:int -> long/float/double + // Serializable -> cast:Byte -> unbox:byte -> byte/short/int/long/float/double + // An marginal case is Number -> dw:Character -> char, which would be OK if there were a + // subclass of Number which wraps a value that can convert to char. + // Since there is none, we don't need an extra check here to cover char or boolean. + return false; + } else { + // R->R always works, since null is always valid dynamically + return true; + } + } + + public interface I {} + + public interface J {} + + public static class CC {} + + public static final class FF {} } diff --git a/test/langtools/tools/javac/lambda/methodReferenceExecution/MethodReferenceIntersection5.java b/test/langtools/tools/javac/lambda/methodReferenceExecution/MethodReferenceIntersection5.java new file mode 100644 index 000000000000..31fc21843885 --- /dev/null +++ b/test/langtools/tools/javac/lambda/methodReferenceExecution/MethodReferenceIntersection5.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021, 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. + */ + +/** + * @test + * @bug 8269121 + * @summary Type inference bug with method references + */ + +public class MethodReferenceIntersection5 { + interface StringLiteral {} + + interface Variable {} + + class MyFact { + static Object make (StringLiteral v) { return null; } + } + + interface OneVariableQuery { + Object query(VarType var1); + } + + static class Interpreter { + Object query(OneVariableQuery query) { return null; } + } + + public static void main(String[] args) { + new Interpreter().query(MyFact::make); + } +}