From a6c653c4b7eaf6d0d32d226f3b119b9428d3038a Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Tue, 7 Sep 2021 23:04:05 -0400 Subject: [PATCH 1/7] 8269121: Type inference bug with method references --- .../sun/tools/javac/comp/LambdaToMethod.java | 45 ++++++++++++++--- .../MethodReferenceIntersection5.java | 50 +++++++++++++++++++ 2 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 test/langtools/tools/javac/lambda/methodReferenceExecution/MethodReferenceIntersection5.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java index c48ac804ac850..fe88d0db2b1c7 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java @@ -44,6 +44,7 @@ import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Type.IntersectionClassType; import com.sun.tools.javac.code.Type.MethodType; import com.sun.tools.javac.code.Type.TypeVar; import com.sun.tools.javac.code.Types; @@ -1084,11 +1085,42 @@ private VarSymbol addParameter(String name, Type p, boolean genArg) { } private MethodType typeToMethodType(Type mt) { + return typeToMethodType(mt, null); + } + + private MethodType typeToMethodType(Type mt, MethodType interfaceMethodType) { Type type = types.erasure(mt); - return new MethodType(type.getParameterTypes(), - type.getReturnType(), - type.getThrownTypes(), - syms.methodClass); + ListBuffer parameterTypes = new ListBuffer<>(); + if (interfaceMethodType != null && mt.getParameterTypes().stream().anyMatch(Type::isIntersection)) { + // we need to make sure that we are selecting the right component of the intersection to erase to + List interfaceParamTypes = interfaceMethodType.getParameterTypes(); + for (Type paramType : mt.getParameterTypes()) { + if (paramType.isIntersection()) { + parameterTypes.add(selectIntersectionComponent((IntersectionClassType)paramType, interfaceParamTypes.head)); + } else { + parameterTypes.add(types.erasure(paramType)); + } + interfaceParamTypes = interfaceParamTypes.tail; + } + } + return new MethodType( + parameterTypes.isEmpty() ? type.getParameterTypes() : parameterTypes.toList(), + type.getReturnType(), + type.getThrownTypes(), + syms.methodClass); + } + + private Type selectIntersectionComponent(IntersectionClassType intersection, Type interfaceParamType) { + for (Type component : intersection.getComponents()) { + // skip object + if (component.tsym == syms.objectType.tsym) { continue; } + Type erasedComponent = types.erasure(component); + if (types.isAssignable(erasedComponent, interfaceParamType)) { + return erasedComponent; + } + } + // bail out + return types.erasure(intersection); } /** @@ -1099,10 +1131,11 @@ private JCExpression makeMetafactoryIndyCall(TranslationContext context, JCFunctionalExpression tree = context.tree; //determine the static bsm args MethodSymbol samSym = (MethodSymbol) types.findDescriptorSymbol(tree.target.tsym); + MethodType intMethodType = typeToMethodType(samSym.type); List staticArgs = List.of( - typeToMethodType(samSym.type), + intMethodType, refSym.asHandle(), - typeToMethodType(tree.getDescriptorType(types))); + typeToMethodType(tree.getDescriptorType(types), intMethodType)); //computed indy arg types ListBuffer indy_args_types = new ListBuffer<>(); 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 0000000000000..31fc218438850 --- /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); + } +} From bd425138bff311914585df522fe50f70d4abf230 Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Wed, 8 Sep 2021 12:58:25 -0400 Subject: [PATCH 2/7] addressing review comments --- .../sun/tools/javac/comp/LambdaToMethod.java | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java index fe88d0db2b1c7..c4f69d4df56f8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java @@ -1090,24 +1090,42 @@ private MethodType typeToMethodType(Type mt) { private MethodType typeToMethodType(Type mt, MethodType interfaceMethodType) { Type type = types.erasure(mt); + List adaptedParams = List.nil(); + if (interfaceMethodType != null && !areParametersAssignable(type, interfaceMethodType)) { + adaptedParams = adaptParameters(mt, interfaceMethodType); + } + return new MethodType( + adaptedParams.isEmpty() ? type.getParameterTypes() : adaptedParams, + type.getReturnType(), + type.getThrownTypes(), + syms.methodClass); + } + + boolean areParametersAssignable(Type erasedMT, MethodType interfaceMethodType) { + List interfaceMParams = interfaceMethodType.getParameterTypes(); + for (Type paramType : erasedMT.getParameterTypes()) { + if (!types.isAssignable(paramType, interfaceMParams.head)) { + return false; + } + interfaceMParams = interfaceMParams.tail; + } + return true; + } + + List adaptParameters(Type mt, MethodType interfaceMethodType) { ListBuffer parameterTypes = new ListBuffer<>(); - if (interfaceMethodType != null && mt.getParameterTypes().stream().anyMatch(Type::isIntersection)) { - // we need to make sure that we are selecting the right component of the intersection to erase to + if (mt.getParameterTypes().stream().anyMatch(Type::isIntersection)) { List interfaceParamTypes = interfaceMethodType.getParameterTypes(); for (Type paramType : mt.getParameterTypes()) { if (paramType.isIntersection()) { - parameterTypes.add(selectIntersectionComponent((IntersectionClassType)paramType, interfaceParamTypes.head)); + parameterTypes.add(selectIntersectionComponent((IntersectionClassType) paramType, interfaceParamTypes.head)); } else { parameterTypes.add(types.erasure(paramType)); } interfaceParamTypes = interfaceParamTypes.tail; } } - return new MethodType( - parameterTypes.isEmpty() ? type.getParameterTypes() : parameterTypes.toList(), - type.getReturnType(), - type.getThrownTypes(), - syms.methodClass); + return parameterTypes.toList(); } private Type selectIntersectionComponent(IntersectionClassType intersection, Type interfaceParamType) { From 579ebdf8455633074dbe468cfcea44f6e185360b Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Mon, 13 Sep 2021 19:17:03 -0400 Subject: [PATCH 3/7] changing the verification of LMF --- .../AbstractValidatingLambdaMetafactory.java | 48 ++++++++++++-- .../sun/tools/javac/comp/LambdaToMethod.java | 63 ++----------------- .../lambda/MetafactoryDescriptorTest.java | 46 ++++++++++++-- 3 files changed, 90 insertions(+), 67 deletions(-) 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 b1282970ccf66..d3be29bf4052e 100644 --- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -26,6 +26,8 @@ import sun.invoke.util.Wrapper; +import java.lang.reflect.Modifier; + import static java.lang.invoke.MethodHandleInfo.*; import static sun.invoke.util.Wrapper.forPrimitiveType; import static sun.invoke.util.Wrapper.forWrapperType; @@ -291,7 +293,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 (!isAdaptableTo(dynamicParamType, implParamType, true, true)) { throw new LambdaConversionException( String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", i, dynamicParamType, implParamType)); @@ -319,9 +321,11 @@ 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)) { - String msg = String.format("Type mismatch for dynamic parameter %d: %s is not a subtype of %s", - i, dynamicParamType, descriptorParamType); + if (!descriptorParamType.isAssignableFrom(dynamicParamType) && + (descriptorParamType.isPrimitive() || dynamicParamType.isPrimitive() || + !sideCastExists(descriptorParamType, dynamicParamType))) { + String msg = String.format("Type mismatch for dynamic parameter %d: %s is not convertible to %s", + i, dynamicParamType, descriptorParamType); throw new LambdaConversionException(msg); } } @@ -343,6 +347,18 @@ private void checkDescriptor(MethodType descriptor) throws LambdaConversionExcep * @return True if 'fromType' can be passed to an argument of 'toType' */ private boolean isAdaptableTo(Class fromType, Class toType, boolean strict) { + return isAdaptableTo(fromType, toType, strict, false); + } + + /** + * Check type adaptability for parameter types. + * @param fromType Type to convert from + * @param toType Type to convert to + * @param strict If true, do strict checks, else allow that fromType may be parameterized + * @param allowSideCast If true, then sicasts are allowed + * @return True if 'fromType' can be passed to an argument of 'toType' + */ + private boolean isAdaptableTo(Class fromType, Class toType, boolean strict, boolean allowSideCast) { if (fromType.equals(toType)) { return true; } @@ -369,12 +385,32 @@ private boolean isAdaptableTo(Class fromType, Class toType, boolean strict return !strict; } } else { - // both are reference types: fromType should be a superclass of toType. - return !strict || toType.isAssignableFrom(fromType); + // both are reference types: fromType should be a superclass of toType or there should exist + // a sidecast from fromType to toType + return !strict || toType.isAssignableFrom(fromType) || (allowSideCast && sideCastExists(fromType, toType)); } } } + /** + * Check if a sidecas exist + * @param fromType Type to convert from + * @param toType Type to convert to + * @return True if a sidecast exists from 'fromType' to 'toType' + */ + private boolean sideCastExists(Class fromType, Class toType) { + if (toType.isInterface() && fromType.isInterface()) { + return true; + } else if (toType.isInterface()) { + return ((fromType.getModifiers() & Modifier.FINAL) == 0); + } else if (fromType.isInterface()) { + return ((toType.getModifiers() & Modifier.FINAL) == 0); + } else if (toType.isArray() && fromType.isArray()) { + return sideCastExists(fromType.getComponentType(), toType.getComponentType()); + } + return false; + } + /** * Check type adaptability for return types -- * special handling of void type) and parameterized fromType diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java index c4f69d4df56f8..c48ac804ac850 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java @@ -44,7 +44,6 @@ import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; -import com.sun.tools.javac.code.Type.IntersectionClassType; import com.sun.tools.javac.code.Type.MethodType; import com.sun.tools.javac.code.Type.TypeVar; import com.sun.tools.javac.code.Types; @@ -1085,60 +1084,11 @@ private VarSymbol addParameter(String name, Type p, boolean genArg) { } private MethodType typeToMethodType(Type mt) { - return typeToMethodType(mt, null); - } - - private MethodType typeToMethodType(Type mt, MethodType interfaceMethodType) { Type type = types.erasure(mt); - List adaptedParams = List.nil(); - if (interfaceMethodType != null && !areParametersAssignable(type, interfaceMethodType)) { - adaptedParams = adaptParameters(mt, interfaceMethodType); - } - return new MethodType( - adaptedParams.isEmpty() ? type.getParameterTypes() : adaptedParams, - type.getReturnType(), - type.getThrownTypes(), - syms.methodClass); - } - - boolean areParametersAssignable(Type erasedMT, MethodType interfaceMethodType) { - List interfaceMParams = interfaceMethodType.getParameterTypes(); - for (Type paramType : erasedMT.getParameterTypes()) { - if (!types.isAssignable(paramType, interfaceMParams.head)) { - return false; - } - interfaceMParams = interfaceMParams.tail; - } - return true; - } - - List adaptParameters(Type mt, MethodType interfaceMethodType) { - ListBuffer parameterTypes = new ListBuffer<>(); - if (mt.getParameterTypes().stream().anyMatch(Type::isIntersection)) { - List interfaceParamTypes = interfaceMethodType.getParameterTypes(); - for (Type paramType : mt.getParameterTypes()) { - if (paramType.isIntersection()) { - parameterTypes.add(selectIntersectionComponent((IntersectionClassType) paramType, interfaceParamTypes.head)); - } else { - parameterTypes.add(types.erasure(paramType)); - } - interfaceParamTypes = interfaceParamTypes.tail; - } - } - return parameterTypes.toList(); - } - - private Type selectIntersectionComponent(IntersectionClassType intersection, Type interfaceParamType) { - for (Type component : intersection.getComponents()) { - // skip object - if (component.tsym == syms.objectType.tsym) { continue; } - Type erasedComponent = types.erasure(component); - if (types.isAssignable(erasedComponent, interfaceParamType)) { - return erasedComponent; - } - } - // bail out - return types.erasure(intersection); + return new MethodType(type.getParameterTypes(), + type.getReturnType(), + type.getThrownTypes(), + syms.methodClass); } /** @@ -1149,11 +1099,10 @@ private JCExpression makeMetafactoryIndyCall(TranslationContext context, JCFunctionalExpression tree = context.tree; //determine the static bsm args MethodSymbol samSym = (MethodSymbol) types.findDescriptorSymbol(tree.target.tsym); - MethodType intMethodType = typeToMethodType(samSym.type); List staticArgs = List.of( - intMethodType, + typeToMethodType(samSym.type), refSym.asHandle(), - typeToMethodType(tree.getDescriptorType(types), intMethodType)); + typeToMethodType(tree.getDescriptorType(types))); //computed indy arg types ListBuffer indy_args_types = new ListBuffer<>(); diff --git a/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java b/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java index a22e5dac2df70..3fedda737ef5f 100644 --- a/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java +++ b/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java @@ -26,7 +26,10 @@ * @bug 8035776 8173587 * @summary metafactory should fail if instantiatedMethodType does not match sam/bridge descriptors */ + import java.lang.invoke.*; +import java.lang.reflect.Modifier; + import java.util.*; public class MetafactoryDescriptorTest { @@ -37,8 +40,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 +53,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 +69,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 { @@ -89,12 +98,14 @@ public static MethodHandle getN(Class c) { public static void main(String... args) { 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++) { + //if (i == j) continue; boolean correctRet = t[j].isAssignableFrom(t[i]) || 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), @@ -103,7 +114,10 @@ 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 = t[j].isAssignableFrom(t[i]) || + (!t[j].isPrimitive() && !t[i].isPrimitive() && sideCastExists(t[j], t[i])); + 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])); @@ -266,4 +280,28 @@ public boolean contains(Class from, Class to) { conversions.put(Boolean.class, boolean.class); } + // test if a sidecast exist from fromType to toType + static boolean sideCastExists(Class fromType, Class toType) { + if (fromType.isPrimitive() || toType.isPrimitive()) { + return false; + } + if (toType.isInterface() && fromType.isInterface()) { + return true; + } else if (toType.isInterface()) { + return ((fromType.getModifiers() & Modifier.FINAL) == 0); + } else if (fromType.isInterface()) { + return ((toType.getModifiers() & Modifier.FINAL) == 0); + } else if (toType.isArray() && fromType.isArray()) { + return sideCastExists(fromType.getComponentType(), toType.getComponentType()); + } + return false; + } + + public interface I {} + + public interface J {} + + public static class CC {} + + public static final class FF {} } From d443a48b0e53c809d1b6807c2f986e4485340ccd Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Tue, 14 Sep 2021 23:15:32 -0400 Subject: [PATCH 4/7] support for sealed classes --- .../AbstractValidatingLambdaMetafactory.java | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) 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 d3be29bf4052e..271ae7c9df292 100644 --- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -394,19 +394,33 @@ private boolean isAdaptableTo(Class fromType, Class toType, boolean strict /** * Check if a sidecas exist - * @param fromType Type to convert from - * @param toType Type to convert to - * @return True if a sidecast exists from 'fromType' to 'toType' + * @param aType first type + * @param anotherType second type + * @return True if 'aType' is not disjoint from 'anotherType' */ - private boolean sideCastExists(Class fromType, Class toType) { - if (toType.isInterface() && fromType.isInterface()) { + private boolean sideCastExists(Class aType, Class anotherType) { + if (aType.isAssignableFrom(anotherType)) { return true; - } else if (toType.isInterface()) { - return ((fromType.getModifiers() & Modifier.FINAL) == 0); - } else if (fromType.isInterface()) { - return ((toType.getModifiers() & Modifier.FINAL) == 0); - } else if (toType.isArray() && fromType.isArray()) { - return sideCastExists(fromType.getComponentType(), toType.getComponentType()); + } + // if non is sealed + if (!anotherType.isSealed() && !aType.isSealed()) { + if (anotherType.isInterface() && aType.isInterface()) { + return true; + } else if (anotherType.isInterface()) { + return ((aType.getModifiers() & Modifier.FINAL) == 0); + } else if (aType.isInterface()) { + return ((anotherType.getModifiers() & Modifier.FINAL) == 0); + } else if (anotherType.isArray() && aType.isArray()) { + return sideCastExists(aType.getComponentType(), anotherType.getComponentType()); + } + } else { + Class sealedOne = aType.isSealed() ? aType : anotherType; + Class other = sealedOne == aType ? anotherType : aType; + for (Class subclass : sealedOne.getPermittedSubclasses()) { + if (sideCastExists(subclass, other)) { + return true; + } + } } return false; } From 3c3944250b9ad42f42009d163ca00b7f8929d964 Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Thu, 16 Sep 2021 00:18:50 -0400 Subject: [PATCH 5/7] implement verification based on conversions used at MethodHandle::asType --- .../AbstractValidatingLambdaMetafactory.java | 120 +++++++++--------- .../lambda/MetafactoryDescriptorTest.java | 73 ++++++++--- 2 files changed, 118 insertions(+), 75 deletions(-) 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 271ae7c9df292..586bf03381796 100644 --- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -26,8 +26,6 @@ import sun.invoke.util.Wrapper; -import java.lang.reflect.Modifier; - import static java.lang.invoke.MethodHandleInfo.*; import static sun.invoke.util.Wrapper.forPrimitiveType; import static sun.invoke.util.Wrapper.forWrapperType; @@ -283,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 (!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)); } } @@ -293,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, true)) { + if (!canConvert(dynamicParamType, implParamType)) { throw new LambdaConversionException( String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", i, dynamicParamType, implParamType)); @@ -303,7 +301,7 @@ void validateMetafactoryArgs() throws LambdaConversionException { // Adaptation match: return type Class expectedType = dynamicMethodType.returnType(); Class actualReturnType = implMethodType.returnType(); - if (!isAdaptableToAsReturn(actualReturnType, expectedType)) { + if (!canConvert(actualReturnType, expectedType)) { throw new LambdaConversionException( String.format("Type mismatch for lambda return: %s is not convertible to %s", actualReturnType, expectedType)); @@ -321,33 +319,72 @@ 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) && - (descriptorParamType.isPrimitive() || dynamicParamType.isPrimitive() || - !sideCastExists(descriptorParamType, dynamicParamType))) { - String msg = String.format("Type mismatch for dynamic parameter %d: %s is not convertible to %s", - i, dynamicParamType, descriptorParamType); + if (!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); } } Class dynamicReturnType = dynamicMethodType.returnType(); Class descriptorReturnType = descriptor.returnType(); - if (!isAdaptableToAsReturnStrict(dynamicReturnType, descriptorReturnType)) { + if (!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); } } - /** - * Check type adaptability for parameter types. - * @param fromType Type to convert from - * @param toType Type to convert to - * @param strict If true, do strict checks, else allow that fromType may be parameterized - * @return True if 'fromType' can be passed to an argument of 'toType' - */ - private boolean isAdaptableTo(Class fromType, Class toType, boolean strict) { - return isAdaptableTo(fromType, toType, strict, false); + 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; + } } /** @@ -355,10 +392,9 @@ private boolean isAdaptableTo(Class fromType, Class toType, boolean strict * @param fromType Type to convert from * @param toType Type to convert to * @param strict If true, do strict checks, else allow that fromType may be parameterized - * @param allowSideCast If true, then sicasts are allowed * @return True if 'fromType' can be passed to an argument of 'toType' */ - private boolean isAdaptableTo(Class fromType, Class toType, boolean strict, boolean allowSideCast) { + private boolean isAdaptableTo(Class fromType, Class toType, boolean strict) { if (fromType.equals(toType)) { return true; } @@ -385,44 +421,10 @@ private boolean isAdaptableTo(Class fromType, Class toType, boolean strict return !strict; } } else { - // both are reference types: fromType should be a superclass of toType or there should exist - // a sidecast from fromType to toType - return !strict || toType.isAssignableFrom(fromType) || (allowSideCast && sideCastExists(fromType, toType)); - } - } - } - - /** - * Check if a sidecas exist - * @param aType first type - * @param anotherType second type - * @return True if 'aType' is not disjoint from 'anotherType' - */ - private boolean sideCastExists(Class aType, Class anotherType) { - if (aType.isAssignableFrom(anotherType)) { - return true; - } - // if non is sealed - if (!anotherType.isSealed() && !aType.isSealed()) { - if (anotherType.isInterface() && aType.isInterface()) { - return true; - } else if (anotherType.isInterface()) { - return ((aType.getModifiers() & Modifier.FINAL) == 0); - } else if (aType.isInterface()) { - return ((anotherType.getModifiers() & Modifier.FINAL) == 0); - } else if (anotherType.isArray() && aType.isArray()) { - return sideCastExists(aType.getComponentType(), anotherType.getComponentType()); - } - } else { - Class sealedOne = aType.isSealed() ? aType : anotherType; - Class other = sealedOne == aType ? anotherType : aType; - for (Class subclass : sealedOne.getPermittedSubclasses()) { - if (sideCastExists(subclass, other)) { - return true; - } + // both are reference types: fromType should be a superclass of toType. + return !strict || toType.isAssignableFrom(fromType); } } - return false; } /** diff --git a/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java b/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java index 3fedda737ef5f..2dab1e14de256 100644 --- a/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java +++ b/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java @@ -23,10 +23,13 @@ /* * @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; @@ -106,7 +109,7 @@ public static void main(String... args) { MethodHandle n = C.getN(t[i]); // null for void.class for (int j = 0; j < t.length; j++) { //if (i == j) continue; - boolean correctRet = t[j].isAssignableFrom(t[i]) || conversions.contains(t[i], t[j]); + 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)); @@ -115,8 +118,7 @@ public static void main(String... args) { if (t[i] != void.class && t[j] != void.class) { //boolean correctParam = t[j].isAssignableFrom(t[i]) || sideCastExists(t[i], t[j]); - boolean correctParam = t[j].isAssignableFrom(t[i]) || - (!t[j].isPrimitive() && !t[i].isPrimitive() && sideCastExists(t[j], t[i])); + 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]), @@ -140,6 +142,10 @@ 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:" + + " impl=" + mh + + ", inst=" + instMT + + ", sam=" + samMT); LambdaMetafactory.metafactory(lookup, "run", mt(I.class), samMT, mh, instMT); if (!correct) { @@ -280,21 +286,56 @@ public boolean contains(Class from, Class to) { conversions.put(Boolean.class, boolean.class); } - // test if a sidecast exist from fromType to toType - static boolean sideCastExists(Class fromType, Class toType) { - if (fromType.isPrimitive() || toType.isPrimitive()) { + 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; - } - if (toType.isInterface() && fromType.isInterface()) { + } else { + // R->R always works, since null is always valid dynamically return true; - } else if (toType.isInterface()) { - return ((fromType.getModifiers() & Modifier.FINAL) == 0); - } else if (fromType.isInterface()) { - return ((toType.getModifiers() & Modifier.FINAL) == 0); - } else if (toType.isArray() && fromType.isArray()) { - return sideCastExists(fromType.getComponentType(), toType.getComponentType()); } - return false; } public interface I {} From 81f6963f4d115fdeda429ecd18b68187dcc0db2a Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Thu, 16 Sep 2021 00:18:50 -0400 Subject: [PATCH 6/7] implement verification based on conversions used at MethodHandle::asType --- .../AbstractValidatingLambdaMetafactory.java | 120 +++++++++--------- .../lambda/MetafactoryDescriptorTest.java | 79 +++++++++--- 2 files changed, 122 insertions(+), 77 deletions(-) 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 271ae7c9df292..586bf03381796 100644 --- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -26,8 +26,6 @@ import sun.invoke.util.Wrapper; -import java.lang.reflect.Modifier; - import static java.lang.invoke.MethodHandleInfo.*; import static sun.invoke.util.Wrapper.forPrimitiveType; import static sun.invoke.util.Wrapper.forWrapperType; @@ -283,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 (!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)); } } @@ -293,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, true)) { + if (!canConvert(dynamicParamType, implParamType)) { throw new LambdaConversionException( String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", i, dynamicParamType, implParamType)); @@ -303,7 +301,7 @@ void validateMetafactoryArgs() throws LambdaConversionException { // Adaptation match: return type Class expectedType = dynamicMethodType.returnType(); Class actualReturnType = implMethodType.returnType(); - if (!isAdaptableToAsReturn(actualReturnType, expectedType)) { + if (!canConvert(actualReturnType, expectedType)) { throw new LambdaConversionException( String.format("Type mismatch for lambda return: %s is not convertible to %s", actualReturnType, expectedType)); @@ -321,33 +319,72 @@ 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) && - (descriptorParamType.isPrimitive() || dynamicParamType.isPrimitive() || - !sideCastExists(descriptorParamType, dynamicParamType))) { - String msg = String.format("Type mismatch for dynamic parameter %d: %s is not convertible to %s", - i, dynamicParamType, descriptorParamType); + if (!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); } } Class dynamicReturnType = dynamicMethodType.returnType(); Class descriptorReturnType = descriptor.returnType(); - if (!isAdaptableToAsReturnStrict(dynamicReturnType, descriptorReturnType)) { + if (!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); } } - /** - * Check type adaptability for parameter types. - * @param fromType Type to convert from - * @param toType Type to convert to - * @param strict If true, do strict checks, else allow that fromType may be parameterized - * @return True if 'fromType' can be passed to an argument of 'toType' - */ - private boolean isAdaptableTo(Class fromType, Class toType, boolean strict) { - return isAdaptableTo(fromType, toType, strict, false); + 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; + } } /** @@ -355,10 +392,9 @@ private boolean isAdaptableTo(Class fromType, Class toType, boolean strict * @param fromType Type to convert from * @param toType Type to convert to * @param strict If true, do strict checks, else allow that fromType may be parameterized - * @param allowSideCast If true, then sicasts are allowed * @return True if 'fromType' can be passed to an argument of 'toType' */ - private boolean isAdaptableTo(Class fromType, Class toType, boolean strict, boolean allowSideCast) { + private boolean isAdaptableTo(Class fromType, Class toType, boolean strict) { if (fromType.equals(toType)) { return true; } @@ -385,44 +421,10 @@ private boolean isAdaptableTo(Class fromType, Class toType, boolean strict return !strict; } } else { - // both are reference types: fromType should be a superclass of toType or there should exist - // a sidecast from fromType to toType - return !strict || toType.isAssignableFrom(fromType) || (allowSideCast && sideCastExists(fromType, toType)); - } - } - } - - /** - * Check if a sidecas exist - * @param aType first type - * @param anotherType second type - * @return True if 'aType' is not disjoint from 'anotherType' - */ - private boolean sideCastExists(Class aType, Class anotherType) { - if (aType.isAssignableFrom(anotherType)) { - return true; - } - // if non is sealed - if (!anotherType.isSealed() && !aType.isSealed()) { - if (anotherType.isInterface() && aType.isInterface()) { - return true; - } else if (anotherType.isInterface()) { - return ((aType.getModifiers() & Modifier.FINAL) == 0); - } else if (aType.isInterface()) { - return ((anotherType.getModifiers() & Modifier.FINAL) == 0); - } else if (anotherType.isArray() && aType.isArray()) { - return sideCastExists(aType.getComponentType(), anotherType.getComponentType()); - } - } else { - Class sealedOne = aType.isSealed() ? aType : anotherType; - Class other = sealedOne == aType ? anotherType : aType; - for (Class subclass : sealedOne.getPermittedSubclasses()) { - if (sideCastExists(subclass, other)) { - return true; - } + // both are reference types: fromType should be a superclass of toType. + return !strict || toType.isAssignableFrom(fromType); } } - return false; } /** diff --git a/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java b/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java index 3fedda737ef5f..29295cc01dfe9 100644 --- a/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java +++ b/test/jdk/java/lang/invoke/lambda/MetafactoryDescriptorTest.java @@ -23,10 +23,13 @@ /* * @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; @@ -96,17 +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, - I.class, J.class, CC.class, FF.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++) { //if (i == j) continue; - boolean correctRet = t[j].isAssignableFrom(t[i]) || conversions.contains(t[i], t[j]); + 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)); @@ -115,8 +119,7 @@ public static void main(String... args) { if (t[i] != void.class && t[j] != void.class) { //boolean correctParam = t[j].isAssignableFrom(t[i]) || sideCastExists(t[i], t[j]); - boolean correctParam = t[j].isAssignableFrom(t[i]) || - (!t[j].isPrimitive() && !t[i].isPrimitive() && sideCastExists(t[j], t[i])); + 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]), @@ -140,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) { @@ -280,21 +288,56 @@ public boolean contains(Class from, Class to) { conversions.put(Boolean.class, boolean.class); } - // test if a sidecast exist from fromType to toType - static boolean sideCastExists(Class fromType, Class toType) { - if (fromType.isPrimitive() || toType.isPrimitive()) { + 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; - } - if (toType.isInterface() && fromType.isInterface()) { + } else { + // R->R always works, since null is always valid dynamically return true; - } else if (toType.isInterface()) { - return ((fromType.getModifiers() & Modifier.FINAL) == 0); - } else if (fromType.isInterface()) { - return ((toType.getModifiers() & Modifier.FINAL) == 0); - } else if (toType.isArray() && fromType.isArray()) { - return sideCastExists(fromType.getComponentType(), toType.getComponentType()); } - return false; } public interface I {} From 1cb206515a8b4b03fe039b6ab1a856e4f4d431e9 Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Wed, 22 Sep 2021 16:18:23 -0400 Subject: [PATCH 7/7] refactoring --- .../AbstractValidatingLambdaMetafactory.java | 62 ++----------------- 1 file changed, 5 insertions(+), 57 deletions(-) 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 586bf03381796..37f9aa90c8837 100644 --- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -281,7 +281,7 @@ void validateMetafactoryArgs() throws LambdaConversionException { for (int i=capturedStart; i implParamType = implMethodType.parameterType(i); Class capturedParamType = factoryType.parameterType(i); - if (!canConvert(capturedParamType, implParamType)) { + if (!MethodType.canConvert(capturedParamType, implParamType)) { throw new LambdaConversionException( 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 (!canConvert(dynamicParamType, implParamType)) { + 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 (!canConvert(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 (!canConvert(dynamicParamType, descriptorParamType)) { + 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,65 +328,13 @@ private void checkDescriptor(MethodType descriptor) throws LambdaConversionExcep Class dynamicReturnType = dynamicMethodType.returnType(); Class descriptorReturnType = descriptor.returnType(); - if (!canConvert(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); } } - 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; - } - } - /** * Check type adaptability for parameter types. * @param fromType Type to convert from