Skip to content

Commit 6a69ca4

Browse files
committed
GROOVY-11993: Support serializable method reference
1 parent e7834f9 commit 6a69ca4

4 files changed

Lines changed: 352 additions & 52 deletions

File tree

src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java

Lines changed: 125 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,37 @@
2222
import org.codehaus.groovy.ast.ClassNode;
2323
import org.codehaus.groovy.ast.MethodNode;
2424
import org.codehaus.groovy.ast.Parameter;
25+
import org.codehaus.groovy.ast.expr.Expression;
26+
import org.codehaus.groovy.ast.expr.MethodCallExpression;
27+
import org.codehaus.groovy.ast.stmt.BlockStatement;
28+
import org.codehaus.groovy.ast.stmt.Statement;
29+
import org.codehaus.groovy.classgen.asm.WriterController;
2530
import org.codehaus.groovy.syntax.RuntimeParserException;
2631
import org.objectweb.asm.Handle;
2732
import org.objectweb.asm.Opcodes;
2833
import org.objectweb.asm.Type;
2934

3035
import java.util.List;
36+
import java.util.Objects;
3137

3238
import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
3339
import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
3440
import static org.codehaus.groovy.ast.ClassHelper.isDynamicTyped;
3541
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
42+
import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
43+
import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE;
3644
import static org.codehaus.groovy.ast.tools.GenericsUtils.hasUnresolvedGenerics;
45+
import static org.codehaus.groovy.ast.tools.GeneralUtils.andX;
46+
import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
47+
import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
48+
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
49+
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
50+
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
51+
import static org.codehaus.groovy.ast.tools.GeneralUtils.eqX;
52+
import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
53+
import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX;
54+
import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
55+
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
3756
import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName;
3857
import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getMethodDescriptor;
3958

@@ -59,6 +78,23 @@ default Handle createBootstrapMethod(final boolean isInterface, final boolean se
5978
}
6079

6180
default Object[] createBootstrapMethodArguments(final String abstractMethodDesc, final int insn, final ClassNode methodOwner, final MethodNode methodNode, final Parameter[] parameters, final boolean serializable) {
81+
Object[] arguments = !serializable ? new Object[3] : new Object[]{null, null, null, 5, 0};
82+
83+
arguments[0] = Type.getMethodType(abstractMethodDesc);
84+
85+
arguments[1] = new Handle(
86+
insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or H_INVOKEINTERFACE (GROOVY-9853)
87+
getClassInternalName(methodOwner.getName()),
88+
methodNode.getName(),
89+
getMethodDescriptor(methodNode),
90+
methodOwner.isInterface());
91+
92+
arguments[2] = createInstantiatedMethodType(abstractMethodDesc, methodNode, parameters);
93+
94+
return arguments;
95+
}
96+
97+
default Type createInstantiatedMethodType(final String abstractMethodDesc, final MethodNode methodNode, final Parameter[] parameters) {
6298
ClassNode returnType = methodNode.getReturnType();
6399
switch (Type.getReturnType(abstractMethodDesc).getSort()) {
64100
case Type.BOOLEAN:
@@ -89,20 +125,7 @@ default Object[] createBootstrapMethodArguments(final String abstractMethodDesc,
89125
returnType = ClassHelper.VOID_TYPE; // GROOVY-10933
90126
}
91127

92-
Object[] arguments = !serializable ? new Object[3] : new Object[]{null, null, null, 5, 0};
93-
94-
arguments[0] = Type.getMethodType(abstractMethodDesc);
95-
96-
arguments[1] = new Handle(
97-
insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or H_INVOKEINTERFACE (GROOVY-9853)
98-
getClassInternalName(methodOwner.getName()),
99-
methodNode.getName(),
100-
getMethodDescriptor(methodNode),
101-
methodOwner.isInterface());
102-
103-
arguments[2] = Type.getMethodType(getMethodDescriptor(returnType, parameters));
104-
105-
return arguments;
128+
return Type.getMethodType(getMethodDescriptor(returnType, parameters));
106129
}
107130

108131
default ClassNode convertParameterType(final ClassNode parameterType, final ClassNode inferredType) {
@@ -156,4 +179,92 @@ default Parameter prependParameter(final List<Parameter> parameterList, final St
156179
parameterList.add(0, parameter);
157180
return parameter;
158181
}
182+
183+
default SerializedLambdaKey createSerializedLambdaKey(final String abstractMethodDesc, final int implMethodKind, final ClassNode implClass, final MethodNode implMethod, final Parameter[] parameters, final ClassNode functionalType, final MethodNode abstractMethod, final int capturedArgCount) {
184+
return new SerializedLambdaKey(
185+
implMethodKind,
186+
getClassInternalName(implClass),
187+
implMethod.getName(),
188+
getMethodDescriptor(implMethod),
189+
getClassInternalName(functionalType.redirect()),
190+
abstractMethod.getName(),
191+
abstractMethodDesc,
192+
createInstantiatedMethodType(abstractMethodDesc, implMethod, parameters).getDescriptor(),
193+
capturedArgCount
194+
);
195+
}
196+
197+
default void addDeserializeLambdaDispatcherEntry(final WriterController controller, final Parameter[] parameters, final SerializedLambdaKey key, final MethodNode helperMethod) {
198+
BlockStatement dispatcher = getOrAddDeserializeLambdaDispatcher(controller, parameters);
199+
MethodCallExpression helperCall = callX(classX(controller.getClassNode()), helperMethod.getName(), args(varX(parameters[0])));
200+
helperCall.setImplicitThis(false);
201+
helperCall.setMethodTarget(helperMethod);
202+
203+
List<Statement> statements = dispatcher.getStatements();
204+
statements.add(statements.size() - 1, ifS(boolX(matchesSerializedLambda(varX(parameters[0]), key)), returnS(helperCall)));
205+
}
206+
207+
default BlockStatement getOrAddDeserializeLambdaDispatcher(final WriterController controller, final Parameter[] parameters) {
208+
ClassNode enclosingClass = controller.getClassNode();
209+
BlockStatement dispatcher = enclosingClass.getNodeMetaData(DeserializeLambdaDispatcher.class);
210+
if (dispatcher != null) {
211+
return dispatcher;
212+
}
213+
214+
dispatcher = new BlockStatement();
215+
dispatcher.addStatement(returnS(callX(classX(ClassHelper.make(Objects.class)), "requireNonNull", args(nullX(), constX("Invalid lambda deserialization")))));
216+
217+
enclosingClass.addSyntheticMethod(
218+
"$deserializeLambda$",
219+
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
220+
OBJECT_TYPE,
221+
parameters,
222+
ClassNode.EMPTY_ARRAY,
223+
dispatcher);
224+
enclosingClass.putNodeMetaData(DeserializeLambdaDispatcher.class, dispatcher);
225+
return dispatcher;
226+
}
227+
228+
default Expression matchesSerializedLambda(final Expression serializedLambda, final SerializedLambdaKey key) {
229+
return andX(
230+
eqX(callX(serializedLambda, "getImplMethodKind"), constX(key.implMethodKind(), true)),
231+
andX(
232+
eqX(callX(serializedLambda, "getImplClass"), constX(key.implClass())),
233+
andX(
234+
eqX(callX(serializedLambda, "getImplMethodName"), constX(key.implMethodName())),
235+
andX(
236+
eqX(callX(serializedLambda, "getImplMethodSignature"), constX(key.implMethodSignature())),
237+
andX(
238+
eqX(callX(serializedLambda, "getFunctionalInterfaceClass"), constX(key.functionalInterfaceClass())),
239+
andX(
240+
eqX(callX(serializedLambda, "getFunctionalInterfaceMethodName"), constX(key.functionalInterfaceMethodName())),
241+
andX(
242+
eqX(callX(serializedLambda, "getFunctionalInterfaceMethodSignature"), constX(key.functionalInterfaceMethodSignature())),
243+
andX(
244+
eqX(callX(serializedLambda, "getInstantiatedMethodType"), constX(key.instantiatedMethodType())),
245+
eqX(callX(serializedLambda, "getCapturedArgCount"), constX(key.capturedArgCount(), true))
246+
)
247+
)
248+
)
249+
)
250+
)
251+
)
252+
)
253+
);
254+
}
255+
256+
default Parameter[] createDeserializeLambdaMethodParams() {
257+
return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, "serializedLambda")};
258+
}
259+
260+
final class DeserializeLambdaDispatcher {
261+
private DeserializeLambdaDispatcher() {
262+
}
263+
}
264+
265+
record SerializedLambdaKey(int implMethodKind, String implClass, String implMethodName, String implMethodSignature,
266+
String functionalInterfaceClass, String functionalInterfaceMethodName,
267+
String functionalInterfaceMethodSignature, String instantiatedMethodType,
268+
int capturedArgCount) {
269+
}
159270
}

src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.codehaus.groovy.ast.InnerClassNode;
2626
import org.codehaus.groovy.ast.MethodNode;
2727
import org.codehaus.groovy.ast.Parameter;
28-
import org.codehaus.groovy.ast.builder.AstStringCompiler;
2928
import org.codehaus.groovy.ast.expr.ClosureExpression;
3029
import org.codehaus.groovy.ast.expr.Expression;
3130
import org.codehaus.groovy.ast.expr.LambdaExpression;
@@ -50,13 +49,10 @@
5049
import static org.codehaus.groovy.ast.ClassHelper.GENERATED_LAMBDA_TYPE;
5150
import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
5251
import static org.codehaus.groovy.ast.ClassHelper.SERIALIZABLE_TYPE;
53-
import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE;
5452
import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE;
5553
import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
5654
import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
5755
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
58-
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
59-
import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
6056
import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
6157
import static org.codehaus.groovy.transform.stc.StaticTypesMarker.CLOSURE_ARGUMENTS;
6258
import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PARAMETER_TYPE;
@@ -107,10 +103,6 @@ public void writeLambda(final LambdaExpression expression) {
107103
writeLambdaFactoryInvocation(functionalType.redirect(), abstractMethod, generatedLambda, serializable);
108104
}
109105

110-
private static Parameter[] createDeserializeLambdaMethodParams() {
111-
return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, "serializedLambda")};
112-
}
113-
114106
private static MethodNode resolveFunctionalInterfaceMethod(final ClassNode functionalType) {
115107
if (functionalType == null || !functionalType.isInterface()) {
116108
return null;
@@ -130,8 +122,17 @@ private void ensureDeserializeLambdaSupport(final LambdaExpression expression, f
130122
return;
131123
}
132124

133-
addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda);
134-
addDeserializeLambdaMethod();
125+
MethodNode helperMethod = addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda);
126+
addDeserializeLambdaDispatcherEntry(controller, createDeserializeLambdaMethodParams(), createSerializedLambdaKey(
127+
createMethodDescriptor(ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE))),
128+
generatedLambda.getMethodHandleKind(),
129+
generatedLambda.lambdaClass,
130+
generatedLambda.lambdaMethod,
131+
generatedLambda.lambdaMethod.getParameters(),
132+
expression.getNodeMetaData(PARAMETER_TYPE),
133+
ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE)),
134+
generatedLambda.isCapturing() ? 1 : 0
135+
), helperMethod);
135136
}
136137

137138
private void writeLambdaFactoryInvocation(final ClassNode functionalType, final MethodNode abstractMethod, final GeneratedLambda generatedLambda, final boolean serializable) {
@@ -327,36 +328,11 @@ private Parameter[] createParametersWithExactType(final LambdaExpression express
327328
return lambdaParameters;
328329
}
329330

330-
private void addDeserializeLambdaMethod() {
331-
ClassNode enclosingClass = controller.getClassNode();
332-
Parameter[] parameters = createDeserializeLambdaMethodParams();
333-
if (enclosingClass.hasMethod("$deserializeLambda$", parameters)) {
334-
return;
335-
}
336-
337-
Statement code = block(
338-
declS(localVarX("enclosingClass", OBJECT_TYPE), classX(enclosingClass)),
339-
((BlockStatement) new AstStringCompiler().compile(
340-
"return enclosingClass" +
341-
".getDeclaredMethod(\"\\$deserializeLambda_${serializedLambda.getImplClass().replace('/', '$')}\\$\", serializedLambda.getClass())" +
342-
".invoke(null, serializedLambda)"
343-
).get(0)).getStatements().get(0)
344-
);
345-
346-
enclosingClass.addSyntheticMethod(
347-
"$deserializeLambda$",
348-
ACC_PRIVATE | ACC_STATIC,
349-
OBJECT_TYPE,
350-
parameters,
351-
ClassNode.EMPTY_ARRAY,
352-
code);
353-
}
354-
355331
private static boolean requiresLambdaInstance(final MethodNode lambdaMethod) {
356332
return 0 == (lambdaMethod.getModifiers() & ACC_STATIC);
357333
}
358334

359-
private void addDeserializeLambdaMethodForLambdaExpression(final LambdaExpression expression, final GeneratedLambda generatedLambda) {
335+
private MethodNode addDeserializeLambdaMethodForLambdaExpression(final LambdaExpression expression, final GeneratedLambda generatedLambda) {
360336
ClassNode enclosingClass = controller.getClassNode();
361337
Statement code;
362338
if (generatedLambda.nonCapturing()) {
@@ -394,6 +370,7 @@ public void visit(final MethodVisitor mv) {
394370
// The deserialize helper preloads the captured receiver before it reuses the original lambda expression.
395371
deserializeLambdaMethod.putNodeMetaData(MetaDataKey.PRELOADED_LAMBDA_RECEIVER, generatedLambda.lambdaClass);
396372
}
373+
return deserializeLambdaMethod;
397374
}
398375

399376
private static String createDeserializeLambdaMethodName(final ClassNode lambdaClass) {

0 commit comments

Comments
 (0)