mirror of
https://github.com/openjdk/jdk.git
synced 2026-06-06 18:53:37 +00:00
8381812: The synthetic $deserializeLambda$ is notably bigger
Reviewed-by: mcimadamore
This commit is contained in:
parent
c6f9c23111
commit
ee53e7569e
@ -106,6 +106,8 @@ import static com.sun.tools.javac.code.Kinds.Kind.TYP;
|
||||
import static com.sun.tools.javac.code.Kinds.Kind.VAR;
|
||||
import static com.sun.tools.javac.code.TypeTag.BOT;
|
||||
import static com.sun.tools.javac.code.TypeTag.VOID;
|
||||
import com.sun.tools.javac.jvm.Target;
|
||||
import com.sun.tools.javac.tree.JCTree.JCThrow;
|
||||
|
||||
/**
|
||||
* This pass desugars lambda expressions into static methods
|
||||
@ -128,6 +130,7 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
private TreeMaker make;
|
||||
private final Types types;
|
||||
private final TransTypes transTypes;
|
||||
private final Target target;
|
||||
private Env<AttrContext> attrEnv;
|
||||
|
||||
/** info about the current class being processed */
|
||||
@ -188,6 +191,7 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
make = TreeMaker.instance(context);
|
||||
types = Types.instance(context);
|
||||
transTypes = TransTypes.instance(context);
|
||||
target = Target.instance(context);
|
||||
Options options = Options.instance(context);
|
||||
dumpLambdaToMethodStats = options.isSet("debug.dumpLambdaToMethodStats");
|
||||
dumpLambdaDeserializationStats = options.isSet("debug.dumpLambdaDeserializationStats");
|
||||
@ -248,7 +252,7 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
/**
|
||||
* list of deserialization cases
|
||||
*/
|
||||
private final Map<String, ListBuffer<JCStatement>> deserializeCases = new HashMap<>();
|
||||
private final Map<String, DeserializationCase> deserializeCases = new HashMap<>();
|
||||
|
||||
/**
|
||||
* deserialize method symbol
|
||||
@ -319,7 +323,7 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
int prevPos = make.pos;
|
||||
try {
|
||||
make.at(tree);
|
||||
kInfo.addMethod(makeDeserializeMethod());
|
||||
makeDeserializeMethod().forEach(kInfo::addMethod);
|
||||
} finally {
|
||||
make.at(prevPos);
|
||||
}
|
||||
@ -647,24 +651,59 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
return trans_block;
|
||||
}
|
||||
|
||||
private JCMethodDecl makeDeserializeMethod() {
|
||||
// When an instance created for a "lambda" is serialized, the type that is
|
||||
// serialized is java.lang.invoke.SerializedLambda.
|
||||
// Its SerializedLambda.readResolve will call method $deserializeLambda$
|
||||
// on the class containing the lambda, passing the SerializedLambda as
|
||||
// a parameter. The $deserializeLambda$ is responsible for recreating the
|
||||
// appropriate instance.
|
||||
//
|
||||
// The $deserializeLambda$ looks like this:
|
||||
// private static Object $deserializeLambda$(final java.lang.invoke.SerializedLambda lambda) {
|
||||
// switch (lambda.getImplMethodName()) {
|
||||
// case <implMethodName> -> return $deserializeLambda$<implMethodName>(lambda);
|
||||
// }
|
||||
// throw new IllegalArgumentException("Invalid lambda deserialization");
|
||||
// }
|
||||
//
|
||||
// The $deserializeLambda$<implMethodName> methods then look like:
|
||||
// private static Object $deserializeLambda$<implMethodName>(final java.lang.invoke.SerializedLambda lambda) {
|
||||
// if (lambda.getImplMethodKind() == ... &&
|
||||
// lambda.getFunctionalInterfaceClass().equals(...) &&
|
||||
// lambda.getFunctionalInterfaceMethodName().equals(...) &&
|
||||
// lambda.getFunctionalInterfaceMethodSignature().equals(...) &&
|
||||
// lambda.getImplClass().equals(...) &&
|
||||
// lambda.getImplMethodSignature().equals(...) &&
|
||||
// lambda.getInstantiatedMethodType().equals(...)) return <recreate-lambda>;
|
||||
// //any additional deserialization cases with the same implMethodName.
|
||||
// throw new IllegalArgumentException("Invalid lambda deserialization");
|
||||
// }
|
||||
//
|
||||
// The $deserializeLambda$<implMethodName> may contain multiple if statements if
|
||||
// there are multiple SerializedLambdas with the same implMethodName name.
|
||||
// This may happen when a method references is serialized.
|
||||
private List<JCMethodDecl> makeDeserializeMethod() {
|
||||
ListBuffer<JCCase> cases = new ListBuffer<>();
|
||||
ListBuffer<JCBreak> breaks = new ListBuffer<>();
|
||||
for (Map.Entry<String, ListBuffer<JCStatement>> entry : kInfo.deserializeCases.entrySet()) {
|
||||
ListBuffer<JCMethodDecl> deserializeMethods = new ListBuffer<>();
|
||||
for (Map.Entry<String, DeserializationCase> entry : kInfo.deserializeCases.entrySet()) {
|
||||
deserializeMethods.append(createImplementationNameDeserializationMethod(entry.getValue()));
|
||||
|
||||
JCBreak br = make.Break(null);
|
||||
breaks.add(br);
|
||||
List<JCStatement> stmts = entry.getValue().append(br).toList();
|
||||
List<JCStatement> stmts = List.of(
|
||||
make.Return(make.App(make.QualIdent(entry.getValue().deserializationMethod), List.of(make.Ident(kInfo.deserParamSym)))),
|
||||
br
|
||||
);
|
||||
cases.add(make.Case(JCCase.STATEMENT, List.of(make.ConstantCaseLabel(make.Literal(entry.getKey()))), null, stmts, null));
|
||||
}
|
||||
JCSwitch sw = make.Switch(deserGetter("getImplMethodName", syms.stringType), cases.toList());
|
||||
JCSwitch sw = make.Switch(deserGetter(kInfo.deserParamSym, "getImplMethodName", syms.stringType), cases.toList());
|
||||
for (JCBreak br : breaks) {
|
||||
br.target = sw;
|
||||
}
|
||||
JCBlock body = make.Block(0L, List.of(
|
||||
sw,
|
||||
make.Throw(makeNewClass(
|
||||
syms.illegalArgumentExceptionType,
|
||||
List.of(make.Literal("Invalid lambda deserialization"))))));
|
||||
createThrowInvalidLambdaDeserialization()));
|
||||
JCMethodDecl deser = make.MethodDef(make.Modifiers(kInfo.deserMethodSym.flags()),
|
||||
names.deserializeLambda,
|
||||
make.QualIdent(kInfo.deserMethodSym.getReturnType().tsym),
|
||||
@ -676,6 +715,32 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
deser.sym = kInfo.deserMethodSym;
|
||||
deser.type = kInfo.deserMethodSym.type;
|
||||
//System.err.printf("DESER: '%s'\n", deser);
|
||||
deserializeMethods.append(lower.translateMethod(attrEnv, deser, make));
|
||||
return deserializeMethods.toList();
|
||||
}
|
||||
|
||||
private JCThrow createThrowInvalidLambdaDeserialization() {
|
||||
return make.Throw(makeNewClass(
|
||||
syms.illegalArgumentExceptionType,
|
||||
List.of(make.Literal("Invalid lambda deserialization"))));
|
||||
}
|
||||
|
||||
private JCMethodDecl createImplementationNameDeserializationMethod(DeserializationCase deserializationCase) {
|
||||
JCBlock body = make.Block(0L,
|
||||
deserializationCase.stmts
|
||||
.append(createThrowInvalidLambdaDeserialization())
|
||||
.toList());
|
||||
JCMethodDecl deser = make.MethodDef(make.Modifiers(deserializationCase.deserializationMethod().flags()),
|
||||
deserializationCase.deserializationMethod().name,
|
||||
make.QualIdent(deserializationCase.deserializationMethod().getReturnType().tsym),
|
||||
List.nil(),
|
||||
List.of(make.VarDef(deserializationCase.deserParamSym(), null)),
|
||||
List.nil(),
|
||||
body,
|
||||
null);
|
||||
deser.sym = deserializationCase.deserializationMethod();
|
||||
deser.type = deserializationCase.deserializationMethod().type;
|
||||
//System.err.printf("DESER: '%s'\n", deser);
|
||||
return lower.translateMethod(attrEnv, deser, make);
|
||||
}
|
||||
|
||||
@ -716,41 +781,55 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
}
|
||||
}
|
||||
String implClass = classSig(types.erasure(refSym.owner.type));
|
||||
String implMethodName = refSym.getQualifiedName().toString();
|
||||
Name implMethodNameAsName = refSym.getQualifiedName();
|
||||
String implMethodName = implMethodNameAsName.toString();
|
||||
String implMethodSignature = typeSig(types.erasure(refSym.type));
|
||||
String instantiatedMethodType = typeSig(types.erasure(samType));
|
||||
|
||||
int implMethodKind = refSym.referenceKind();
|
||||
JCExpression kindTest = eqTest(syms.intType, deserGetter("getImplMethodKind", syms.intType),
|
||||
|
||||
DeserializationCase deserializationCase = kInfo.deserializeCases.computeIfAbsent(implMethodName, _ -> {
|
||||
Name currentDeserializationMethodName = implMethodNameAsName == names.init
|
||||
? names.deserializeLambda.append(names.fromString("init"))
|
||||
: names.deserializeLambda.append(target.syntheticNameChar(), implMethodNameAsName);
|
||||
MethodSymbol caseDeserializationMethod = makePrivateSyntheticMethod(STATIC, currentDeserializationMethodName,
|
||||
kInfo.deserMethodSym.type, kInfo.clazz.sym);
|
||||
VarSymbol caseDeserializationParam = new VarSymbol(FINAL, names.fromString("lambda"),
|
||||
syms.serializedLambdaType, caseDeserializationMethod);
|
||||
return new DeserializationCase(caseDeserializationMethod, caseDeserializationParam, new ListBuffer<>());
|
||||
});
|
||||
VarSymbol deserParamSym = deserializationCase.deserParamSym();
|
||||
|
||||
JCExpression kindTest = eqTest(syms.intType, deserGetter(deserParamSym, "getImplMethodKind", syms.intType),
|
||||
make.Literal(implMethodKind));
|
||||
ListBuffer<JCExpression> serArgs = new ListBuffer<>();
|
||||
int i = 0;
|
||||
for (Type t : indyType.getParameterTypes()) {
|
||||
List<JCExpression> indexAsArg = new ListBuffer<JCExpression>().append(make.Literal(i)).toList();
|
||||
List<Type> argTypes = new ListBuffer<Type>().append(syms.intType).toList();
|
||||
serArgs.add(make.TypeCast(types.erasure(t), deserGetter("getCapturedArg", syms.objectType, argTypes, indexAsArg)));
|
||||
serArgs.add(make.TypeCast(types.erasure(t), deserGetter(deserParamSym, "getCapturedArg", syms.objectType, argTypes, indexAsArg)));
|
||||
++i;
|
||||
}
|
||||
JCStatement stmt = make.If(
|
||||
deserTest(deserTest(deserTest(deserTest(deserTest(deserTest(
|
||||
kindTest,
|
||||
"getFunctionalInterfaceClass", functionalInterfaceClass),
|
||||
"getFunctionalInterfaceMethodName", functionalInterfaceMethodName),
|
||||
"getFunctionalInterfaceMethodSignature", functionalInterfaceMethodSignature),
|
||||
"getImplClass", implClass),
|
||||
"getImplMethodSignature", implMethodSignature),
|
||||
"getInstantiatedMethodType", instantiatedMethodType),
|
||||
deserTest(deserParamSym,
|
||||
deserTest(deserParamSym,
|
||||
deserTest(deserParamSym,
|
||||
deserTest(deserParamSym,
|
||||
deserTest(deserParamSym,
|
||||
deserTest(deserParamSym,
|
||||
kindTest,
|
||||
"getFunctionalInterfaceClass", functionalInterfaceClass),
|
||||
"getFunctionalInterfaceMethodName", functionalInterfaceMethodName),
|
||||
"getFunctionalInterfaceMethodSignature", functionalInterfaceMethodSignature),
|
||||
"getImplClass", implClass),
|
||||
"getImplMethodSignature", implMethodSignature),
|
||||
"getInstantiatedMethodType", instantiatedMethodType),
|
||||
make.Return(makeIndyCall(
|
||||
pos,
|
||||
syms.lambdaMetafactory,
|
||||
names.altMetafactory,
|
||||
staticArgs, indyType, serArgs.toList(), samSym.name)),
|
||||
null);
|
||||
ListBuffer<JCStatement> stmts = kInfo.deserializeCases.get(implMethodName);
|
||||
if (stmts == null) {
|
||||
stmts = new ListBuffer<>();
|
||||
kInfo.deserializeCases.put(implMethodName, stmts);
|
||||
}
|
||||
if (dumpLambdaDeserializationStats) {
|
||||
log.note(pos, Notes.LambdaDeserializationStat(
|
||||
functionalInterfaceClass,
|
||||
@ -762,7 +841,7 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
implMethodSignature,
|
||||
instantiatedMethodType));
|
||||
}
|
||||
stmts.append(stmt);
|
||||
deserializationCase.stmts().append(stmt);
|
||||
}
|
||||
|
||||
private JCExpression eqTest(Type argType, JCExpression arg1, JCExpression arg2) {
|
||||
@ -772,12 +851,12 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
return testExpr;
|
||||
}
|
||||
|
||||
private JCExpression deserTest(JCExpression prev, String func, String lit) {
|
||||
private JCExpression deserTest(VarSymbol deserParamSym, JCExpression prev, String func, String lit) {
|
||||
MethodType eqmt = new MethodType(List.of(syms.objectType), syms.booleanType, List.nil(), syms.methodClass);
|
||||
Symbol eqsym = rs.resolveQualifiedMethod(null, attrEnv, syms.objectType, names.equals, List.of(syms.objectType), List.nil());
|
||||
JCMethodInvocation eqtest = make.Apply(
|
||||
List.nil(),
|
||||
make.Select(deserGetter(func, syms.stringType), eqsym).setType(eqmt),
|
||||
make.Select(deserGetter(deserParamSym, func, syms.stringType), eqsym).setType(eqmt),
|
||||
List.of(make.Literal(lit)));
|
||||
eqtest.setType(syms.booleanType);
|
||||
JCBinary compound = make.Binary(Tag.AND, prev, eqtest);
|
||||
@ -786,16 +865,16 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
return compound;
|
||||
}
|
||||
|
||||
private JCExpression deserGetter(String func, Type type) {
|
||||
return deserGetter(func, type, List.nil(), List.nil());
|
||||
private JCExpression deserGetter(VarSymbol deserParamSym, String func, Type type) {
|
||||
return deserGetter(deserParamSym, func, type, List.nil(), List.nil());
|
||||
}
|
||||
|
||||
private JCExpression deserGetter(String func, Type type, List<Type> argTypes, List<JCExpression> args) {
|
||||
private JCExpression deserGetter(VarSymbol deserParamSym, String func, Type type, List<Type> argTypes, List<JCExpression> args) {
|
||||
MethodType getmt = new MethodType(argTypes, type, List.nil(), syms.methodClass);
|
||||
Symbol getsym = rs.resolveQualifiedMethod(null, attrEnv, syms.serializedLambdaType, names.fromString(func), argTypes, List.nil());
|
||||
return make.Apply(
|
||||
List.nil(),
|
||||
make.Select(make.Ident(kInfo.deserParamSym).setType(syms.serializedLambdaType), getsym).setType(getmt),
|
||||
make.Select(make.Ident(deserParamSym).setType(syms.serializedLambdaType), getsym).setType(getmt),
|
||||
args).setType(type);
|
||||
}
|
||||
|
||||
@ -1273,6 +1352,14 @@ public class LambdaToMethod extends TreeTranslator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialization statements for a given lambda implementation name, together
|
||||
* with the (future) enclosing deserialization method.
|
||||
*/
|
||||
record DeserializationCase(MethodSymbol deserializationMethod,
|
||||
VarSymbol deserParamSym,
|
||||
ListBuffer<JCStatement> stmts) {}
|
||||
|
||||
/**
|
||||
* ****************************************************************
|
||||
* Signature Generation
|
||||
|
||||
388
test/langtools/tools/javac/lambda/ManyLambdasSerialization.java
Normal file
388
test/langtools/tools/javac/lambda/ManyLambdasSerialization.java
Normal file
@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright (c) 2026, 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 8381812
|
||||
* @summary Check that serializable lambda desugaring can handle many serializable lambdas
|
||||
* @library /tools/lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.compiler/com.sun.tools.javac.util
|
||||
* @compile ManyLambdasSerialization.java
|
||||
* @build toolbox.ToolBox
|
||||
* @run junit ManyLambdasSerialization
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import toolbox.JavacTask;
|
||||
import toolbox.JavaTask;
|
||||
import toolbox.Task;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
public class ManyLambdasSerialization {
|
||||
private static final int LAMBDA_COUNT = 1000;
|
||||
private final ToolBox tb = new ToolBox();
|
||||
|
||||
@Test
|
||||
public void testManySerializableLambdasDifferentImplMethodName() throws IOException {
|
||||
List<String> lambdaMethods = new ArrayList<>();
|
||||
List<String> tests = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < LAMBDA_COUNT; i++) {
|
||||
String index = Integer.toString(i);
|
||||
|
||||
lambdaMethods.add("""
|
||||
private static Supplier<Integer> create${INDEX}() {
|
||||
return (Supplier<Integer> & Serializable) () -> ${INDEX};
|
||||
}
|
||||
""".replace("${INDEX}", index));
|
||||
tests.add(" runTest(${INDEX}, create${INDEX}());\n".replace("${INDEX}", index));
|
||||
}
|
||||
|
||||
StringBuilder code = new StringBuilder();
|
||||
code.append("""
|
||||
import java.io.*;
|
||||
import java.util.function.Supplier;
|
||||
class Test {
|
||||
""");
|
||||
|
||||
lambdaMethods.forEach(code::append);
|
||||
|
||||
code.append("""
|
||||
private static void runTest(int expectedResult, Supplier<Integer> instance) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
oos.writeObject(instance);
|
||||
oos.close();
|
||||
}
|
||||
try (ByteArrayInputStream in = new ByteArrayInputStream(baos.toByteArray());
|
||||
ObjectInputStream ois = new ObjectInputStream(in)) {
|
||||
int actual = ((Supplier<Integer>) ois.readObject()).get();
|
||||
if (expectedResult != actual) {
|
||||
throw new AssertionError("Expected: " + expectedResult + ", actual: " + actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main() throws Exception {
|
||||
""");
|
||||
|
||||
tests.forEach(code::append);
|
||||
|
||||
code.append("""
|
||||
System.err.println("OK");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
new JavacTask(tb)
|
||||
.sources(code.toString())
|
||||
.outdir(".")
|
||||
.run()
|
||||
.writeAll();
|
||||
|
||||
List<String> output = new JavaTask(tb)
|
||||
.classpath(".")
|
||||
.className("Test")
|
||||
.run()
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.STDERR);
|
||||
|
||||
tb.checkEqual(List.of("OK"), output);
|
||||
}
|
||||
|
||||
// @Test The current deserialization does not support too many serialized lambdas with the same implementation method name
|
||||
public void testManySerializableLambdasSameImplMethodName() {
|
||||
List<String> lambdaMethods = new ArrayList<>();
|
||||
List<String> tests = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < LAMBDA_COUNT; i++) {
|
||||
String index = Integer.toString(i);
|
||||
|
||||
lambdaMethods.add("""
|
||||
private static Function<Box${INDEX}, Box${INDEX}> create${INDEX}() {
|
||||
return (Function<Box${INDEX}, Box${INDEX}> & Serializable) Test::id;
|
||||
}
|
||||
record Box${INDEX}(int i) {}
|
||||
""".replace("${INDEX}", index));
|
||||
tests.add("""
|
||||
runTest(create${INDEX}(), t -> {
|
||||
int expectedResult = ${INDEX};
|
||||
//in case of a bad deserialization, the implicit cast here would fail:
|
||||
int actual = t.apply(new Box${INDEX}(expectedResult)).i();
|
||||
if (expectedResult != actual) {
|
||||
throw new AssertionError("Expected: " + expectedResult + ", actual: " + actual);
|
||||
}
|
||||
});
|
||||
""".replace("${INDEX}", index));
|
||||
}
|
||||
|
||||
StringBuilder code = new StringBuilder();
|
||||
code.append("""
|
||||
import java.io.*;
|
||||
import java.util.function.*;
|
||||
class Test {
|
||||
""");
|
||||
|
||||
lambdaMethods.forEach(code::append);
|
||||
|
||||
code.append("""
|
||||
private static <T> T id(T t) { return t; }
|
||||
private static <T> void runTest(Function<T, T> testInstance, Consumer<Function<T, T>> checker) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
oos.writeObject(testInstance);
|
||||
oos.close();
|
||||
}
|
||||
try (ByteArrayInputStream in = new ByteArrayInputStream(baos.toByteArray());
|
||||
ObjectInputStream ois = new ObjectInputStream(in)) {
|
||||
checker.accept((Function<T, T>) ois.readObject());
|
||||
}
|
||||
}
|
||||
|
||||
public static void main() throws Exception {
|
||||
""");
|
||||
|
||||
tests.forEach(code::append);
|
||||
|
||||
code.append("""
|
||||
System.err.println("OK");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
new JavacTask(tb)
|
||||
.sources(code.toString())
|
||||
.outdir(".")
|
||||
.run()
|
||||
.writeAll();
|
||||
|
||||
List<String> output = new JavaTask(tb)
|
||||
.classpath(".")
|
||||
.className("Test")
|
||||
.run()
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.STDERR);
|
||||
|
||||
tb.checkEqual(List.of("OK"), output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManySerializableLambdasCapturing() {
|
||||
List<String> lambdaMethods = new ArrayList<>();
|
||||
List<String> tests = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < LAMBDA_COUNT; i++) {
|
||||
String index = Integer.toString(i);
|
||||
int capturedValues = 10;
|
||||
|
||||
lambdaMethods.add("""
|
||||
private static Supplier<Integer> create${INDEX}() {
|
||||
${DECLARATIONS}
|
||||
return (Supplier<Integer> & Serializable) () -> ${CAPTURED};
|
||||
}
|
||||
""".replace("${INDEX}", index)
|
||||
.replace("${DECLARATIONS}", Stream.iterate(0, v -> v + 1).limit(capturedValues).map(v -> "int v" + v + " = " + index + ";\n").collect(Collectors.joining()))
|
||||
.replace("${CAPTURED}", Stream.iterate(0, v -> v + 1).limit(capturedValues).map(v -> "v" + v).collect(Collectors.joining(" + "))));
|
||||
tests.add(" runTest(${EXPECTED}, create${INDEX}());\n".replace("${INDEX}", index).replace("${EXPECTED}", String.valueOf(capturedValues * i)));
|
||||
}
|
||||
|
||||
StringBuilder code = new StringBuilder();
|
||||
code.append("""
|
||||
import java.io.*;
|
||||
import java.util.function.Supplier;
|
||||
class Test {
|
||||
""");
|
||||
|
||||
lambdaMethods.forEach(code::append);
|
||||
|
||||
code.append("""
|
||||
private static void runTest(int expectedResult, Supplier<Integer> instance) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
oos.writeObject(instance);
|
||||
oos.close();
|
||||
}
|
||||
try (ByteArrayInputStream in = new ByteArrayInputStream(baos.toByteArray());
|
||||
ObjectInputStream ois = new ObjectInputStream(in)) {
|
||||
int actual = ((Supplier<Integer>) ois.readObject()).get();
|
||||
if (expectedResult != actual) {
|
||||
throw new AssertionError("Expected: " + expectedResult + ", actual: " + actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main() throws Exception {
|
||||
""");
|
||||
|
||||
tests.forEach(code::append);
|
||||
|
||||
code.append("""
|
||||
System.err.println("OK");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
new JavacTask(tb)
|
||||
.sources(code.toString())
|
||||
.outdir(".")
|
||||
.run()
|
||||
.writeAll();
|
||||
|
||||
List<String> output = new JavaTask(tb)
|
||||
.classpath(".")
|
||||
.className("Test")
|
||||
.run()
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.STDERR);
|
||||
|
||||
tb.checkEqual(List.of("OK"), output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVeryManySerializableLambdasCapturing() {
|
||||
List<String> lambdaMethods = new ArrayList<>();
|
||||
List<String> tests = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < LAMBDA_COUNT; i++) {
|
||||
String index = Integer.toString(i);
|
||||
int capturedValues = 200;
|
||||
|
||||
lambdaMethods.add("""
|
||||
private static Supplier<Integer> create${INDEX}() {
|
||||
${DECLARATIONS}
|
||||
return (Supplier<Integer> & Serializable) () -> ${CAPTURED};
|
||||
}
|
||||
""".replace("${INDEX}", index)
|
||||
.replace("${DECLARATIONS}", Stream.iterate(0, v -> v + 1).limit(capturedValues).map(v -> "int v" + v + " = " + index + ";\n").collect(Collectors.joining()))
|
||||
.replace("${CAPTURED}", Stream.iterate(0, v -> v + 1).limit(capturedValues).map(v -> "v" + v).collect(Collectors.joining(" + "))));
|
||||
tests.add(" runTest(${EXPECTED}, create${INDEX}());\n".replace("${INDEX}", index).replace("${EXPECTED}", String.valueOf(capturedValues * i)));
|
||||
}
|
||||
|
||||
StringBuilder code = new StringBuilder();
|
||||
code.append("""
|
||||
import java.io.*;
|
||||
import java.util.function.Supplier;
|
||||
class Test {
|
||||
""");
|
||||
|
||||
lambdaMethods.forEach(code::append);
|
||||
|
||||
code.append("""
|
||||
private static void runTest(int expectedResult, Supplier<Integer> instance) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
oos.writeObject(instance);
|
||||
oos.close();
|
||||
}
|
||||
try (ByteArrayInputStream in = new ByteArrayInputStream(baos.toByteArray());
|
||||
ObjectInputStream ois = new ObjectInputStream(in)) {
|
||||
int actual = ((Supplier<Integer>) ois.readObject()).get();
|
||||
if (expectedResult != actual) {
|
||||
throw new AssertionError("Expected: " + expectedResult + ", actual: " + actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main() throws Exception {
|
||||
""");
|
||||
|
||||
tests.forEach(code::append);
|
||||
|
||||
code.append("""
|
||||
System.err.println("OK");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
new JavacTask(tb)
|
||||
.sources(code.toString())
|
||||
.options("-XDdeserializableLambdaCaseCountLimit=15")
|
||||
.outdir(".")
|
||||
.run()
|
||||
.writeAll();
|
||||
|
||||
List<String> output = new JavaTask(tb)
|
||||
.classpath(".")
|
||||
.className("Test")
|
||||
.run()
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.STDERR);
|
||||
|
||||
tb.checkEqual(List.of("OK"), output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyWeirdImplNameWorks() {
|
||||
//make sure method references with name "init" and "<init>"
|
||||
//don't clash in deserialization method name:
|
||||
String code = """
|
||||
import java.io.*;
|
||||
import java.util.function.Supplier;
|
||||
public class Test {
|
||||
public static Test init() { return new Test("factory"); }
|
||||
public Test() { this("constructor"); }
|
||||
|
||||
private final String origin;
|
||||
private Test(String origin) { this.origin = origin;}
|
||||
public static void main() throws Exception {
|
||||
Supplier<Test> fromConstructor = (Supplier<Test> & Serializable) Test::new;
|
||||
Supplier<Test> fromFactory = (Supplier<Test> & Serializable) Test::init;
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
oos.writeObject(fromConstructor);
|
||||
oos.writeObject(fromFactory);
|
||||
oos.close();
|
||||
}
|
||||
try (ByteArrayInputStream in = new ByteArrayInputStream(baos.toByteArray());
|
||||
ObjectInputStream ois = new ObjectInputStream(in)) {
|
||||
System.err.println(((Supplier<Test>) ois.readObject()).get().origin);
|
||||
System.err.println(((Supplier<Test>) ois.readObject()).get().origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
new JavacTask(tb)
|
||||
.sources(code.toString())
|
||||
.outdir(".")
|
||||
.run()
|
||||
.writeAll();
|
||||
|
||||
List<String> output = new JavaTask(tb)
|
||||
.classpath(".")
|
||||
.className("Test")
|
||||
.run()
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.STDERR);
|
||||
|
||||
tb.checkEqual(List.of("constructor", "factory"), output);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user