This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch GROOVY-11993 in repository https://gitbox.apache.org/repos/asf/groovy.git
commit c6d921519818623b75db549e4d04c9c52241fa82 Author: Daniel Sun <[email protected]> AuthorDate: Tue May 5 02:33:46 2026 +0900 GROOVY-11993: Support serializable method reference --- .../asm/sc/AbstractFunctionalInterfaceWriter.java | 306 ++++++++++++++---- .../classgen/asm/sc/StaticTypesLambdaWriter.java | 73 ++--- ...StaticTypesMethodReferenceExpressionWriter.java | 103 +++++- .../transform/stc/MethodReferenceTest.groovy | 349 +++++++++++++++++++++ 4 files changed, 707 insertions(+), 124 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java index ceb280c43b..83c6da20f0 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java @@ -22,45 +22,183 @@ import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.tools.GeneralUtils; +import org.codehaus.groovy.classgen.asm.BytecodeHelper; +import org.codehaus.groovy.classgen.asm.WriterController; import org.codehaus.groovy.syntax.RuntimeParserException; import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import java.util.List; +import java.util.Arrays; +import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE; import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper; import static org.codehaus.groovy.ast.ClassHelper.getWrapper; import static org.codehaus.groovy.ast.ClassHelper.isDynamicTyped; import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; +import static org.codehaus.groovy.ast.tools.GeneralUtils.args; +import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.classX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.eqX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.param; +import static org.codehaus.groovy.ast.tools.GeneralUtils.params; +import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; import static org.codehaus.groovy.ast.tools.GenericsUtils.hasUnresolvedGenerics; import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName; import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getMethodDescriptor; +import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET; +import static org.objectweb.asm.Opcodes.CHECKCAST; /** - * Represents functional interface writer which contains some common methods to complete generating bytecode + * Shared bytecode and deserialization support for statically-compiled functional interface implementations, + * including both lambdas and method references. + * * @since 3.0.0 */ public interface AbstractFunctionalInterfaceWriter { + default void writeFunctionalInterfaceIndy(final MethodVisitor methodVisitor, + final String samMethodName, final String invokedTypeDescriptor, + final String samMethodDescriptor, final int implementationKind, + final ClassNode implementationOwner, final MethodNode implementationMethod, + final Parameter[] implementationParameters, final boolean serializable) { + methodVisitor.visitInvokeDynamicInsn( + samMethodName, + invokedTypeDescriptor, + createBootstrapMethod(serializable), + createBootstrapMethodArguments(samMethodDescriptor, implementationKind, implementationOwner, implementationMethod, implementationParameters, serializable) + ); + if (serializable) { + methodVisitor.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(ClassHelper.SERIALIZABLE_TYPE)); + } + } + default String createMethodDescriptor(final MethodNode method) { return getMethodDescriptor(method.getReturnType(), method.getParameters()); } - default Handle createBootstrapMethod(final boolean isInterface, final boolean serializable) { + default ClassNode convertParameterType(final ClassNode targetType, final ClassNode parameterType, final ClassNode inferredType) { + if (!getWrapper(inferredType).isDerivedFrom(getWrapper(parameterType))) { + throw new RuntimeParserException("The inferred type[" + inferredType.redirect() + "] is not compatible with the parameter type[" + parameterType.redirect() + "]", parameterType); + } + + ClassNode type; + if (isPrimitiveType(parameterType)) { + if (!isPrimitiveType(inferredType)) { + // The non-primitive type and primitive type are not allowed to mix since Java 9+ + // java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: class java.lang.Integer is not a subtype of int + type = getUnwrapper(inferredType).getPlainNodeReference(false); + } else { + type = inferredType.getPlainNodeReference(false); + } + } else if (isPrimitiveType(inferredType)) { + // GROOVY-9790: bootstrap method initialization exception raised when lambda parameter type is wrong + // (1) java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: class java.lang.Integer is not a subtype of int + // (2) java.lang.BootstrapMethodError: bootstrap method initialization exception + if (!(isDynamicTyped(parameterType) && isPrimitiveType(targetType)) // (1) + && (parameterType.equals(getUnwrapper(parameterType)) || inferredType.equals(getWrapper(inferredType)))) { // (2) + // The non-primitive type and primitive type are not allowed to mix since Java 9+ + // java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: int is not a subtype of class java.lang.Object + type = getWrapper(inferredType).getPlainNodeReference(); + } else { + type = inferredType.getPlainNodeReference(false); + } + } else { + type = inferredType; + // GROOVY-11304: no placeholders + if (hasUnresolvedGenerics(type)) type = type.redirect(); + // GROOVY-11479: mutable for node metadata or type annotations + if (type.toString(false).equals(parameterType.toString(false))) { + type = parameterType; + } else { + // TODO: deep copy if type args set + type = type.getPlainNodeReference(); + } + } + return type; + } + + default SerializedFunctionalInterface createSerializedFunctionalInterface(final String samMethodDescriptor, final int implementationKind, + final ClassNode implementationOwner, final MethodNode implementationMethod, + final Parameter[] implementationParameters, final ClassNode functionalType, + final MethodNode abstractMethod, final int capturedArgumentCount) { + return new SerializedFunctionalInterface( + implementationKind, + getClassInternalName(implementationOwner), + implementationMethod.getName(), + getMethodDescriptor(implementationMethod), + getClassInternalName(functionalType.redirect()), + abstractMethod.getName(), + samMethodDescriptor, + createInstantiatedMethodType(samMethodDescriptor, implementationMethod, implementationParameters).getDescriptor(), + capturedArgumentCount + ); + } + + default void addDeserializeDispatcherEntry(final WriterController controller, final Parameter[] deserializeMethodParameters, + final SerializedFunctionalInterface serializedFunctionalInterface, + final MethodNode helperMethod) { + BlockStatement dispatcherGuards = getOrAddDeserializeDispatcherGuards(controller, deserializeMethodParameters); + MethodCallExpression helperCall = callX(classX(controller.getClassNode()), helperMethod.getName(), args(varX(deserializeMethodParameters[0]))); + helperCall.setImplicitThis(false); + helperCall.setMethodTarget(helperMethod); + + dispatcherGuards.addStatement( + // Keep this guard strict: deserialization must route to exactly one synthetic helper + // whose serialized-lambda fingerprint fully matches the incoming SerializedLambda. + ifS(boolX(matchesSerializedFunctionalInterface(varX(deserializeMethodParameters[0]), serializedFunctionalInterface)), + returnS(helperCall) + ) + ); + } + + private Handle createBootstrapMethod(final boolean serializable) { return new Handle( - Opcodes.H_INVOKESTATIC, - "java/lang/invoke/LambdaMetafactory", - serializable ? "altMetafactory" : "metafactory", - serializable ? "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;" - : "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", - false // GROOVY-8299, GROOVY-8989, GROOVY-11265 + Opcodes.H_INVOKESTATIC, + "java/lang/invoke/LambdaMetafactory", + serializable ? "altMetafactory" : "metafactory", + serializable ? "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;" + : "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", + false // GROOVY-8299, GROOVY-8989, GROOVY-11265 ); } - default Object[] createBootstrapMethodArguments(final String abstractMethodDesc, final int insn, final ClassNode methodOwner, final MethodNode methodNode, final Parameter[] parameters, final boolean serializable) { - ClassNode returnType = methodNode.getReturnType(); - switch (Type.getReturnType(abstractMethodDesc).getSort()) { + private Object[] createBootstrapMethodArguments(final String samMethodDescriptor, final int implementationKind, + final ClassNode implementationOwner, final MethodNode implementationMethod, + final Parameter[] implementationParameters, final boolean serializable) { + Object[] arguments = !serializable ? new Object[3] : new Object[]{null, null, null, 5, 0}; + + arguments[0] = Type.getMethodType(samMethodDescriptor); + + arguments[1] = new Handle( + implementationKind, // H_INVOKESTATIC or H_INVOKEVIRTUAL or H_INVOKEINTERFACE (GROOVY-9853) + getClassInternalName(implementationOwner.getName()), + implementationMethod.getName(), + getMethodDescriptor(implementationMethod), + implementationOwner.isInterface()); + + arguments[2] = createInstantiatedMethodType(samMethodDescriptor, implementationMethod, implementationParameters); + + return arguments; + } + + private Type createInstantiatedMethodType(final String samMethodDescriptor, final MethodNode implementationMethod, final Parameter[] implementationParameters) { + ClassNode returnType = implementationMethod.getReturnType(); + switch (Type.getReturnType(samMethodDescriptor).getSort()) { case Type.BOOLEAN: if (returnType.isGenericsPlaceHolder()) returnType = ClassHelper.Boolean_TYPE; // GROOVY-10975 break; @@ -89,71 +227,107 @@ public interface AbstractFunctionalInterfaceWriter { returnType = ClassHelper.VOID_TYPE; // GROOVY-10933 } - Object[] arguments = !serializable ? new Object[3] : new Object[]{null, null, null, 5, 0}; + return Type.getMethodType(getMethodDescriptor(returnType, implementationParameters)); + } - arguments[0] = Type.getMethodType(abstractMethodDesc); + private BlockStatement getOrAddDeserializeDispatcherGuards(final WriterController controller, final Parameter[] deserializeMethodParameters) { + ClassNode enclosingClass = controller.getClassNode(); + BlockStatement dispatcherGuards = enclosingClass.getNodeMetaData(DeserializeDispatcherMarker.class); + if (dispatcherGuards != null) { + return dispatcherGuards; + } - arguments[1] = new Handle( - insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or H_INVOKEINTERFACE (GROOVY-9853) - getClassInternalName(methodOwner.getName()), - methodNode.getName(), - getMethodDescriptor(methodNode), - methodOwner.isInterface()); + dispatcherGuards = new BlockStatement(); + BlockStatement dispatcher = new BlockStatement(); + dispatcher.addStatement(dispatcherGuards); + dispatcher.addStatement(createInvalidDeserializationStatement()); - arguments[2] = Type.getMethodType(getMethodDescriptor(returnType, parameters)); + enclosingClass.addSyntheticMethod( + "$deserializeLambda$", + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + OBJECT_TYPE, + deserializeMethodParameters, + ClassNode.EMPTY_ARRAY, + dispatcher); + enclosingClass.putNodeMetaData(DeserializeDispatcherMarker.class, dispatcherGuards); + return dispatcherGuards; + } - return arguments; + private Statement createInvalidDeserializationStatement() { + final ClassNode cn = ClassHelper.make(IllegalArgumentException.class); + ConstructorCallExpression exception = ctorX(cn, constX("Invalid serialized functional interface")); + exception.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, cn.getDeclaredConstructor( + params(param(ClassHelper.STRING_TYPE, "message")) + )); + return throwS(exception); } - default ClassNode convertParameterType(final ClassNode parameterType, final ClassNode inferredType) { - return convertParameterType(parameterType, parameterType, inferredType); + /** + * Builds the identity check used by {@code $deserializeLambda$} to select the + * correct synthetic helper for a serialized lambda/method reference. + * <p> + * The generated expression is a conjunction over all stable + * {@link java.lang.invoke.SerializedLambda} identity fields we emit via + * {@link #createSerializedFunctionalInterface(String, int, ClassNode, MethodNode, Parameter[], ClassNode, MethodNode, int)}: + * implementation kind/class/name/signature, functional interface class/SAM method/signature, + * instantiated method type, and captured argument count. + * <p> + * Do not weaken this predicate (for example, by checking only method name/class or + * by returning a constant). Doing so can misroute deserialization to the wrong helper, + * while overly strict constant-false behavior breaks valid deserialization. + * + * @param serializedForm the deserialized {@code SerializedLambda} expression + * @param serializedFunctionalInterface compile-time fingerprint of one serialized lambda target + * @return expression that evaluates to {@code true} only for this exact target + */ + private Expression matchesSerializedFunctionalInterface(final Expression serializedForm, final SerializedFunctionalInterface serializedFunctionalInterface) { + return allMatch( + matchesSerializedFormInt(serializedForm, "getImplMethodKind", serializedFunctionalInterface.implMethodKind()), + matchesSerializedFormString(serializedForm, "getImplClass", serializedFunctionalInterface.implClass()), + matchesSerializedFormString(serializedForm, "getImplMethodName", serializedFunctionalInterface.implMethodName()), + matchesSerializedFormString(serializedForm, "getImplMethodSignature", serializedFunctionalInterface.implMethodSignature()), + matchesSerializedFormString(serializedForm, "getFunctionalInterfaceClass", serializedFunctionalInterface.functionalInterfaceClass()), + matchesSerializedFormString(serializedForm, "getFunctionalInterfaceMethodName", serializedFunctionalInterface.functionalInterfaceMethodName()), + matchesSerializedFormString(serializedForm, "getFunctionalInterfaceMethodSignature", serializedFunctionalInterface.functionalInterfaceMethodSignature()), + matchesSerializedFormString(serializedForm, "getInstantiatedMethodType", serializedFunctionalInterface.instantiatedMethodType()), + matchesSerializedFormInt(serializedForm, "getCapturedArgCount", serializedFunctionalInterface.capturedArgCount()) + ); } - default ClassNode convertParameterType(final ClassNode targetType, final ClassNode parameterType, final ClassNode inferredType) { - if (!getWrapper(inferredType).isDerivedFrom(getWrapper(parameterType))) { - throw new RuntimeParserException("The inferred type[" + inferredType.redirect() + "] is not compatible with the parameter type[" + parameterType.redirect() + "]", parameterType); - } + /** + * Combines predicates with logical AND so all serialized-lambda identity fields + * must match before a helper is selected. + * + * @param expressions match predicates to combine + * @return conjunction of all predicates + * @throws IllegalArgumentException if no predicates are supplied + */ + private Expression allMatch(final Expression... expressions) { + return Arrays.stream(expressions) + .reduce(GeneralUtils::andX) + .orElseThrow(() -> new IllegalArgumentException("expressions must not be empty")); + } - ClassNode type; - if (isPrimitiveType(parameterType)) { - if (!isPrimitiveType(inferredType)) { - // The non-primitive type and primitive type are not allowed to mix since Java 9+ - // java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: class java.lang.Integer is not a subtype of int - type = getUnwrapper(inferredType).getPlainNodeReference(false); - } else { - type = inferredType.getPlainNodeReference(false); - } - } else if (isPrimitiveType(inferredType)) { - // GROOVY-9790: bootstrap method initialization exception raised when lambda parameter type is wrong - // (1) java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: class java.lang.Integer is not a subtype of int - // (2) java.lang.BootstrapMethodError: bootstrap method initialization exception - if (!(isDynamicTyped(parameterType) && isPrimitiveType(targetType)) // (1) - && (parameterType.equals(getUnwrapper(parameterType)) || inferredType.equals(getWrapper(inferredType)))) { // (2) - // The non-primitive type and primitive type are not allowed to mix since Java 9+ - // java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: int is not a subtype of class java.lang.Object - type = getWrapper(inferredType).getPlainNodeReference(); - } else { - type = inferredType.getPlainNodeReference(false); - } - } else { - type = inferredType; - // GROOVY-11304: no placeholders - if (hasUnresolvedGenerics(type)) type = type.redirect(); - // GROOVY-11479: mutable for node metadata or type annotations - if (type.toString(false).equals(parameterType.toString(false))) { - type = parameterType; - } else { - // TODO: deep copy if type args set - type = type.getPlainNodeReference(); - } + private Expression matchesSerializedFormInt(final Expression serializedForm, final String accessorName, final int expectedValue) { + return eqX(callX(serializedForm, accessorName), constX(expectedValue, true)); + } + + private Expression matchesSerializedFormString(final Expression serializedForm, final String accessorName, final String expectedValue) { + return eqX(callX(serializedForm, accessorName), constX(expectedValue)); + } + + default Parameter[] createDeserializeMethodParameters() { + return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, "serializedLambda")}; + } + + final class DeserializeDispatcherMarker { + private DeserializeDispatcherMarker() { } - return type; } - default Parameter prependParameter(final List<Parameter> parameterList, final String parameterName, final ClassNode parameterType) { - Parameter parameter = new Parameter(parameterType, parameterName); - parameter.setClosureSharedVariable(false); - parameterList.add(0, parameter); - return parameter; + record SerializedFunctionalInterface(int implMethodKind, String implClass, String implMethodName, String implMethodSignature, + String functionalInterfaceClass, String functionalInterfaceMethodName, + String functionalInterfaceMethodSignature, String instantiatedMethodType, + int capturedArgCount) { } } diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java index b61dc6c930..68b7d9111a 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java @@ -25,11 +25,8 @@ import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; -import org.codehaus.groovy.ast.builder.AstStringCompiler; import org.codehaus.groovy.ast.expr.ClosureExpression; -import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.LambdaExpression; -import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.classgen.BytecodeInstruction; import org.codehaus.groovy.classgen.BytecodeSequence; @@ -50,18 +47,14 @@ import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE; import static org.codehaus.groovy.ast.ClassHelper.GENERATED_LAMBDA_TYPE; import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; import static org.codehaus.groovy.ast.ClassHelper.SERIALIZABLE_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE; import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE; import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe; import static org.codehaus.groovy.ast.tools.GeneralUtils.block; import static org.codehaus.groovy.ast.tools.GeneralUtils.classX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.declS; -import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX; import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; import static org.codehaus.groovy.transform.stc.StaticTypesMarker.CLOSURE_ARGUMENTS; import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PARAMETER_TYPE; import static org.objectweb.asm.Opcodes.ACC_FINAL; -import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_STATIC; import static org.objectweb.asm.Opcodes.ALOAD; @@ -107,10 +100,6 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun writeLambdaFactoryInvocation(functionalType.redirect(), abstractMethod, generatedLambda, serializable); } - private static Parameter[] createDeserializeLambdaMethodParams() { - return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, "serializedLambda")}; - } - private static MethodNode resolveFunctionalInterfaceMethod(final ClassNode functionalType) { if (functionalType == null || !functionalType.isInterface()) { return null; @@ -130,23 +119,31 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun return; } - addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda); - addDeserializeLambdaMethod(); + MethodNode helperMethod = addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda); + addDeserializeDispatcherEntry(controller, createDeserializeMethodParameters(), createSerializedFunctionalInterface( + createMethodDescriptor(ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE))), + generatedLambda.getMethodHandleKind(), + generatedLambda.lambdaClass, + generatedLambda.lambdaMethod, + generatedLambda.lambdaMethod.getParameters(), + expression.getNodeMetaData(PARAMETER_TYPE), + ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE)), + generatedLambda.isCapturing() ? 1 : 0 + ), helperMethod); } private void writeLambdaFactoryInvocation(final ClassNode functionalType, final MethodNode abstractMethod, final GeneratedLambda generatedLambda, final boolean serializable) { - MethodVisitor mv = controller.getMethodVisitor(); - mv.visitInvokeDynamicInsn( + writeFunctionalInterfaceIndy( + controller.getMethodVisitor(), abstractMethod.getName(), createLambdaFactoryMethodDescriptor(functionalType, generatedLambda), - createBootstrapMethod(controller.getClassNode().isInterface(), serializable), - createBootstrapMethodArguments(createMethodDescriptor(abstractMethod), - generatedLambda.getMethodHandleKind(), - generatedLambda.lambdaClass, generatedLambda.lambdaMethod, generatedLambda.lambdaMethod.getParameters(), serializable) + createMethodDescriptor(abstractMethod), + generatedLambda.getMethodHandleKind(), + generatedLambda.lambdaClass, + generatedLambda.lambdaMethod, + generatedLambda.lambdaMethod.getParameters(), + serializable ); - if (serializable) { - mv.visitTypeInsn(CHECKCAST, "java/io/Serializable"); - } if (generatedLambda.nonCapturing()) { controller.getOperandStack().push(functionalType); @@ -156,7 +153,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun } private boolean hasDeserializeLambdaMethod(final ClassNode lambdaClass) { - return controller.getClassNode().hasMethod(createDeserializeLambdaMethodName(lambdaClass), createDeserializeLambdaMethodParams()); + return controller.getClassNode().hasMethod(createDeserializeLambdaMethodName(lambdaClass), createDeserializeMethodParameters()); } private static MethodNode getLambdaMethod(final ClassNode lambdaClass) { @@ -327,36 +324,11 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun return lambdaParameters; } - private void addDeserializeLambdaMethod() { - ClassNode enclosingClass = controller.getClassNode(); - Parameter[] parameters = createDeserializeLambdaMethodParams(); - if (enclosingClass.hasMethod("$deserializeLambda$", parameters)) { - return; - } - - Statement code = block( - declS(localVarX("enclosingClass", OBJECT_TYPE), classX(enclosingClass)), - ((BlockStatement) new AstStringCompiler().compile( - "return enclosingClass" + - ".getDeclaredMethod(\"\\$deserializeLambda_${serializedLambda.getImplClass().replace('/', '$')}\\$\", serializedLambda.getClass())" + - ".invoke(null, serializedLambda)" - ).get(0)).getStatements().get(0) - ); - - enclosingClass.addSyntheticMethod( - "$deserializeLambda$", - ACC_PRIVATE | ACC_STATIC, - OBJECT_TYPE, - parameters, - ClassNode.EMPTY_ARRAY, - code); - } - private static boolean requiresLambdaInstance(final MethodNode lambdaMethod) { return 0 == (lambdaMethod.getModifiers() & ACC_STATIC); } - private void addDeserializeLambdaMethodForLambdaExpression(final LambdaExpression expression, final GeneratedLambda generatedLambda) { + private MethodNode addDeserializeLambdaMethodForLambdaExpression(final LambdaExpression expression, final GeneratedLambda generatedLambda) { ClassNode enclosingClass = controller.getClassNode(); Statement code; if (generatedLambda.nonCapturing()) { @@ -387,13 +359,14 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun createDeserializeLambdaMethodName(generatedLambda.lambdaClass), ACC_PUBLIC | ACC_STATIC, OBJECT_TYPE, - createDeserializeLambdaMethodParams(), + createDeserializeMethodParameters(), ClassNode.EMPTY_ARRAY, code); if (generatedLambda.isCapturing()) { // The deserialize helper preloads the captured receiver before it reuses the original lambda expression. deserializeLambdaMethod.putNodeMetaData(MetaDataKey.PRELOADED_LAMBDA_RECEIVER, generatedLambda.lambdaClass); } + return deserializeLambdaMethod; } private static String createDeserializeLambdaMethodName(final ClassNode lambdaClass) { diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java index c526caa7b4..75cb7139fe 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java @@ -31,6 +31,8 @@ import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.MethodReferenceExpression; import org.codehaus.groovy.ast.tools.GeneralUtils; +import org.codehaus.groovy.classgen.BytecodeInstruction; +import org.codehaus.groovy.classgen.BytecodeSequence; import org.codehaus.groovy.classgen.AsmClassGenerator; import org.codehaus.groovy.classgen.asm.BytecodeHelper; import org.codehaus.groovy.classgen.asm.MethodReferenceExpressionWriter; @@ -40,7 +42,7 @@ import org.codehaus.groovy.syntax.RuntimeParserException; import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; import org.codehaus.groovy.transform.stc.ExtensionMethodNode; import org.codehaus.groovy.transform.stc.StaticTypesMarker; -import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.Arrays; @@ -60,6 +62,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; import static org.codehaus.groovy.ast.tools.GenericsUtils.extractPlaceholders; import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafe0; +import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName; import static org.codehaus.groovy.ast.tools.ParameterUtils.isVargs; import static org.codehaus.groovy.ast.tools.ParameterUtils.parametersCompatible; import static org.codehaus.groovy.runtime.ArrayGroovyMethods.last; @@ -68,6 +71,13 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.filter import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isAssignableTo; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.resolveClassNodeGenerics; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_STATIC; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ARETURN; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; /** * Generates bytecode for method reference expressions in statically-compiled code. @@ -192,17 +202,23 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE String methodName = abstractMethod.getName(); String methodDesc = BytecodeHelper.getMethodDescriptor(functionalType.redirect(), isClassExpression ? Parameter.EMPTY_ARRAY : new Parameter[]{new Parameter(typeOrTargetRefType, "__METHODREF_EXPR_INSTANCE")}); + boolean serializable = functionalType.implementsInterface(ClassHelper.SERIALIZABLE_TYPE); + if (serializable) { + ensureDeserializeLambdaSupport(methodReferenceExpression, functionalType, abstractMethod, methodRefMethod, parametersWithExactType, + referenceKind, !isClassExpression, typeOrTargetRefType, methodDesc); + } - Handle bootstrapMethod = createBootstrapMethod(classNode.isInterface(), false); - Object[] bootstrapArgs = createBootstrapMethodArguments( + writeFunctionalInterfaceIndy( + controller.getMethodVisitor(), + methodName, + methodDesc, createMethodDescriptor(abstractMethod), referenceKind, methodRefMethod.getDeclaringClass(), methodRefMethod, parametersWithExactType, - false + serializable ); - controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, methodDesc, bootstrapMethod, bootstrapArgs); if (isClassExpression) { controller.getOperandStack().push(functionalType); @@ -255,7 +271,7 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE methodCall.setMethodTarget(mn); methodCall.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, mn); - String methodName = "class$" + classType.getNameWithoutPackage() + "$" + mn.getName() + "$" + System.nanoTime(); + String methodName = createSyntheticMethodName("class", classType, mn.getName()); ClassNode returnType = resolveClassNodeGenerics(Map.of(new GenericsType.GenericsTypeName("T"), new GenericsType(classType)), null, mn.getReturnType()); @@ -297,7 +313,7 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE methodCall.setMethodTarget(mn); methodCall.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, mn); - String methodName = "adapt$" + mn.getDeclaringClass().getNameWithoutPackage() + "$" + mn.getName() + "$" + System.nanoTime(); + String methodName = createSyntheticMethodName("adapt", mn.getDeclaringClass(), mn.getName()); MethodNode delegateMethod = addSyntheticMethod(methodName, mn.getReturnType(), methodCall, parameters, mn.getExceptions()); if (!isStaticTarget && !mn.isStatic()) delegateMethod.setModifiers(delegateMethod.getModifiers() & ~Opcodes.ACC_STATIC); @@ -346,7 +362,7 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE if (inferredParamType == null) continue; Parameter parameter = parameters[i]; - ClassNode type = convertParameterType(parameter.getType(), inferredParamType); + ClassNode type = convertParameterType(parameter.getType(), parameter.getType(), inferredParamType); parameter.setOriginType(type); parameter.setType(type); } @@ -454,8 +470,79 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE throw new MultipleCompilationErrorsException(controller.getSourceUnit().getErrorCollector()); } + private void ensureDeserializeLambdaSupport(final MethodReferenceExpression methodReferenceExpression, final ClassNode functionalType, + final MethodNode abstractMethod, final MethodNode methodRefMethod, final Parameter[] parametersWithExactType, + final int referenceKind, final boolean capturing, final ClassNode capturedTargetType, final String methodDesc) { + String helperName = methodReferenceExpression.getNodeMetaData(MetaDataKey.DESERIALIZE_LAMBDA_METHOD_NAME); + if (helperName == null) { + helperName = createDeserializeLambdaMethodName(); + methodReferenceExpression.putNodeMetaData(MetaDataKey.DESERIALIZE_LAMBDA_METHOD_NAME, helperName); + } + + Parameter[] parameters = createDeserializeMethodParameters(); + if (controller.getClassNode().hasMethod(helperName, parameters)) { + return; + } + + String abstractMethodDesc = createMethodDescriptor(abstractMethod); + MethodNode helperMethod = addDeserializeLambdaMethodForMethodReference(helperName, abstractMethod, methodRefMethod, + parametersWithExactType, referenceKind, capturing, capturedTargetType, methodDesc, abstractMethodDesc); + addDeserializeDispatcherEntry(controller, parameters, + createSerializedFunctionalInterface(abstractMethodDesc, referenceKind, methodRefMethod.getDeclaringClass(), methodRefMethod, + parametersWithExactType, functionalType, abstractMethod, capturing ? 1 : 0), + helperMethod); + } + + private MethodNode addDeserializeLambdaMethodForMethodReference(final String methodName, final MethodNode abstractMethod, + final MethodNode methodRefMethod, final Parameter[] parametersWithExactType, + final int referenceKind, final boolean capturing, + final ClassNode capturedTargetType, final String methodDesc, + final String abstractMethodDesc) { + return controller.getClassNode().addSyntheticMethod( + methodName, + ACC_PRIVATE | ACC_STATIC, + ClassHelper.OBJECT_TYPE, + createDeserializeMethodParameters(), + ClassNode.EMPTY_ARRAY, + new BytecodeSequence(new BytecodeInstruction() { + @Override + public void visit(final MethodVisitor mv) { + if (capturing) { + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(ICONST_0); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/SerializedLambda", "getCapturedArg", "(I)Ljava/lang/Object;", false); + mv.visitTypeInsn(CHECKCAST, getClassInternalName(capturedTargetType)); + } + writeFunctionalInterfaceIndy( + mv, + abstractMethod.getName(), + methodDesc, + abstractMethodDesc, + referenceKind, + methodRefMethod.getDeclaringClass(), + methodRefMethod, + parametersWithExactType, + true + ); + mv.visitInsn(ARETURN); + } + })); + } + + private String createSyntheticMethodName(final String prefix, final ClassNode owner, final String name) { + return prefix + "$" + owner.getNameWithoutPackage() + "$" + name + "$" + controller.getNextHelperMethodIndex(); + } + + private String createDeserializeLambdaMethodName() { + return "$deserializeLambda_methodref$" + controller.getNextHelperMethodIndex() + "$"; + } + //-------------------------------------------------------------------------- + private enum MetaDataKey { + DESERIALIZE_LAMBDA_METHOD_NAME + } + private static boolean isBridgeMethod(final MethodNode mn) { int staticSynthetic = Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC; return ((mn.getModifiers() & staticSynthetic) == staticSynthetic) && mn.getName().startsWith("access$"); diff --git a/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy b/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy index 3c6b5e1b90..6fddb3fcae 100644 --- a/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy +++ b/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy @@ -1158,6 +1158,142 @@ final class MethodReferenceTest { ''' } + @Test // class::new + void testSerializableConstructorReference() { + assertScript shell, ''' + import java.io.ByteArrayInputStream + import java.io.ByteArrayOutputStream + import java.io.Serializable + + @CompileStatic + class C { + interface SerFunc<I, O> extends Serializable, Function<I, O> {} + + static class Box { + final String value + + Box(String value) { + this.value = value.trim() + } + } + + static SerFunc<String, Box> create() { + Box::new + } + + static byte[] serialize(Serializable value) { + def out = new ByteArrayOutputStream() + out.withObjectOutputStream { it.writeObject(value) } + out.toByteArray() + } + + static <T> T deserialize(byte[] bytes) { + new ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) { + (T) it.readObject() + } + } + } + + assert C.declaredMethods.count { it.name == '$deserializeLambda$' } == 1 + + C.SerFunc<String, C.Box> factory = C.deserialize(C.serialize(C.create())) + assert factory instanceof Serializable + assert factory.apply(' ok ').value == 'ok' + ''' + } + + @Test // arrayClass::new + void testSerializableArrayConstructorReference() { + assertScript shell, ''' + import java.io.ByteArrayInputStream + import java.io.ByteArrayOutputStream + import java.io.Serializable + + @CompileStatic + class C { + interface SerIntFunc<T> extends Serializable, IntFunction<T> {} + + static SerIntFunc<String[]> create() { + String[]::new + } + + static byte[] serialize(Serializable value) { + def out = new ByteArrayOutputStream() + out.withObjectOutputStream { it.writeObject(value) } + out.toByteArray() + } + + static <T> T deserialize(byte[] bytes) { + new ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) { + (T) it.readObject() + } + } + } + + C.SerIntFunc<String[]> factory = C.deserialize(C.serialize(C.create())) + String[] values = factory.apply(3) + assert values.length == 3 + assert values.toList() == [null, null, null] + ''' + } + + @Test + void testSerializableConstructorReferencesShareDeserializeDispatcherWithLambdas() { + assertScript shell, ''' + import java.io.ByteArrayInputStream + import java.io.ByteArrayOutputStream + import java.io.Serializable + + @CompileStatic + class C { + interface SerFunc<I, O> extends Serializable, Function<I, O> {} + interface SerIntFunc<T> extends Serializable, IntFunction<T> {} + + static class Box { + final String value + + Box(String value) { + this.value = value + } + } + + static SerFunc<String, Box> createConstructorReference() { + Box::new + } + + static SerIntFunc<Box[]> createArrayConstructorReference() { + Box[]::new + } + + static SerFunc<Integer, String> createLambda() { + (Integer i) -> 'L' + i + } + + static byte[] serialize(Serializable value) { + def out = new ByteArrayOutputStream() + out.withObjectOutputStream { it.writeObject(value) } + out.toByteArray() + } + + static <T> T deserialize(byte[] bytes) { + new ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) { + (T) it.readObject() + } + } + } + + assert C.declaredMethods.count { it.name == '$deserializeLambda$' } == 1 + + C.SerFunc<String, C.Box> ctor = C.deserialize(C.serialize(C.createConstructorReference())) + C.SerIntFunc<C.Box[]> arrayCtor = C.deserialize(C.serialize(C.createArrayConstructorReference())) + C.SerFunc<Integer, String> lambda = C.deserialize(C.serialize(C.createLambda())) + + assert ctor.apply('box').value == 'box' + assert arrayCtor.apply(2).length == 2 + assert lambda.apply(4) == 'L4' + ''' + } + @Test // class::staticMethod void testFunctionCS() { assertScript shell, ''' @@ -1662,6 +1798,219 @@ final class MethodReferenceTest { ''' } + @Test + void testSerializableNonCapturingMethodReference() { + assertScript shell, ''' + import java.io.ByteArrayInputStream + import java.io.ByteArrayOutputStream + import java.io.Serializable + + @CompileStatic + class C { + interface SerFunc<I, O> extends Serializable, Function<I, O> {} + + static SerFunc<Integer, String> create() { + Integer::toString + } + + static byte[] serialize(Serializable value) { + def out = new ByteArrayOutputStream() + out.withObjectOutputStream { it.writeObject(value) } + out.toByteArray() + } + + static <T> T deserialize(byte[] bytes) { + new ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) { + (T) it.readObject() + } + } + } + + assert C.declaredMethods.count { it.name == '$deserializeLambda$' } == 1 + + C.SerFunc<Integer, String> fn = C.deserialize(C.serialize(C.create())) + assert fn instanceof Serializable + assert fn.apply(7) == '7' + ''' + } + + @Test + void testSerializableCapturingMethodReference() { + assertScript shell, ''' + import java.io.ByteArrayInputStream + import java.io.ByteArrayOutputStream + import java.io.Serializable + + @CompileStatic + class C { + interface SerSupplier<T> extends Serializable, Supplier<T> {} + + private final String text + + C(String text) { + this.text = text + } + + SerSupplier<String> create() { + text::trim + } + + static byte[] serialize(Serializable value) { + def out = new ByteArrayOutputStream() + out.withObjectOutputStream { it.writeObject(value) } + out.toByteArray() + } + + static <T> T deserialize(byte[] bytes) { + new ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) { + (T) it.readObject() + } + } + } + + C.SerSupplier<String> supplier = C.deserialize(C.serialize(new C(' answer ').create())) + assert supplier instanceof Serializable + assert supplier.get() == 'answer' + ''' + } + + @Test + void testSerializableMethodReferencesShareDeserializeDispatcherWithLambdas() { + assertScript shell, ''' + import java.io.ByteArrayInputStream + import java.io.ByteArrayOutputStream + import java.io.Serializable + + @CompileStatic + class C { + interface SerFunc<I, O> extends Serializable, Function<I, O> {} + interface SerSupplier<T> extends Serializable, Supplier<T> {} + + private final String text + + C(String text) { + this.text = text + } + + static SerFunc<Integer, String> createMethodReference() { + Integer::toString + } + + static SerFunc<Integer, String> createLambda() { + (Integer i) -> 'L' + i + } + + SerSupplier<String> createBoundMethodReference() { + text::trim + } + + static byte[] serialize(Serializable value) { + def out = new ByteArrayOutputStream() + out.withObjectOutputStream { it.writeObject(value) } + out.toByteArray() + } + + static <T> T deserialize(byte[] bytes) { + new ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) { + (T) it.readObject() + } + } + } + + assert C.declaredMethods.count { it.name == '$deserializeLambda$' } == 1 + + C.SerFunc<Integer, String> methodRef = C.deserialize(C.serialize(C.createMethodReference())) + C.SerFunc<Integer, String> lambda = C.deserialize(C.serialize(C.createLambda())) + C.SerSupplier<String> bound = C.deserialize(C.serialize(new C(' x ').createBoundMethodReference())) + + assert methodRef.apply(3) == '3' + assert lambda.apply(4) == 'L4' + assert bound.get() == 'x' + ''' + } + + @Test + void testDeserializeDispatcherReturnsMatchingMethodReferenceAndLambdaBeforeFallback() { + assertScript shell, ''' + import java.io.Serializable + import java.lang.invoke.SerializedLambda + + @CompileStatic + class C { + interface SerFunc<I, O> extends Serializable, Function<I, O> {} + + static SerFunc<Integer, String> createMethodReference() { + Integer::toString + } + + static SerFunc<Integer, String> createLambda() { + (Integer i) -> 'L' + i + } + + @CompileDynamic + static SerializedLambda serialized(Serializable value) { + def writeReplace = value.class.getDeclaredMethod('writeReplace') + writeReplace.accessible = true + (SerializedLambda) writeReplace.invoke(value) + } + } + + def dispatcher = C.getDeclaredMethod('$deserializeLambda$', SerializedLambda) + dispatcher.accessible = true + + C.SerFunc<Integer, String> methodRef = + (C.SerFunc<Integer, String>) dispatcher.invoke(null, C.serialized(C.createMethodReference())) + C.SerFunc<Integer, String> lambda = + (C.SerFunc<Integer, String>) dispatcher.invoke(null, C.serialized(C.createLambda())) + + assert methodRef.apply(3) == '3' + assert lambda.apply(3) == 'L3' + ''' + } + + @Test + void testDeserializeDispatcherReportsClearErrorForMismatchedSerializedForm() { + assertScript shell, ''' + import java.lang.invoke.MethodHandleInfo + import java.lang.invoke.SerializedLambda + + @CompileStatic + class C { + interface SerFunc<I, O> extends Serializable, Function<I, O> {} + + static SerFunc<Integer, String> create() { + Integer::toString + } + } + + def dispatcher = C.getDeclaredMethod('$deserializeLambda$', SerializedLambda) + dispatcher.accessible = true + + def serialized = new SerializedLambda( + C, + 'java/util/function/Function', + 'apply', + '(Ljava/lang/Object;)Ljava/lang/Object;', + MethodHandleInfo.REF_invokeStatic, + 'java/lang/Integer', + 'toString', + '(I)Ljava/lang/String;', + '(Ljava/lang/Integer;)Ljava/lang/String;', + [] as Object[] + ) + + def err + try { + dispatcher.invoke(null, serialized) + assert false: 'dispatcher invocation should fail' + } catch (java.lang.reflect.InvocationTargetException e) { + err = e + } + assert err.cause instanceof IllegalArgumentException + assert err.cause.message == 'Invalid serialized functional interface' + ''' + } + // GROOVY-11467 @Test void testSuperInterfaceMethodReference() {
