/* * 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 lambdaMethods = new ArrayList<>(); List tests = new ArrayList<>(); for (int i = 0; i < LAMBDA_COUNT; i++) { String index = Integer.toString(i); lambdaMethods.add(""" private static Supplier create${INDEX}() { return (Supplier & 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 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) 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 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 lambdaMethods = new ArrayList<>(); List tests = new ArrayList<>(); for (int i = 0; i < LAMBDA_COUNT; i++) { String index = Integer.toString(i); lambdaMethods.add(""" private static Function create${INDEX}() { return (Function & 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 id(T t) { return t; } private static void runTest(Function testInstance, Consumer> 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) 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 output = new JavaTask(tb) .classpath(".") .className("Test") .run() .writeAll() .getOutputLines(Task.OutputKind.STDERR); tb.checkEqual(List.of("OK"), output); } @Test public void testManySerializableLambdasCapturing() { List lambdaMethods = new ArrayList<>(); List tests = new ArrayList<>(); for (int i = 0; i < LAMBDA_COUNT; i++) { String index = Integer.toString(i); int capturedValues = 10; lambdaMethods.add(""" private static Supplier create${INDEX}() { ${DECLARATIONS} return (Supplier & 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 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) 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 output = new JavaTask(tb) .classpath(".") .className("Test") .run() .writeAll() .getOutputLines(Task.OutputKind.STDERR); tb.checkEqual(List.of("OK"), output); } @Test public void testVeryManySerializableLambdasCapturing() { List lambdaMethods = new ArrayList<>(); List tests = new ArrayList<>(); for (int i = 0; i < LAMBDA_COUNT; i++) { String index = Integer.toString(i); int capturedValues = 200; lambdaMethods.add(""" private static Supplier create${INDEX}() { ${DECLARATIONS} return (Supplier & 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 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) 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 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 "" //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 fromConstructor = (Supplier & Serializable) Test::new; Supplier fromFactory = (Supplier & 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) ois.readObject()).get().origin); System.err.println(((Supplier) ois.readObject()).get().origin); } } } """; new JavacTask(tb) .sources(code.toString()) .outdir(".") .run() .writeAll(); List output = new JavaTask(tb) .classpath(".") .className("Test") .run() .writeAll() .getOutputLines(Task.OutputKind.STDERR); tb.checkEqual(List.of("constructor", "factory"), output); } }