Add test where lambda desugaring overflow class file method size
Bug: b/225839019
Change-Id: I93a89c758caef46192d5d801612dfe02a8bb01c3
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdasInHugeMethod.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdasInHugeMethod.java
new file mode 100644
index 0000000..45f1165
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdasInHugeMethod.java
@@ -0,0 +1,263 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+@RunWith(Parameterized.class)
+public class LambdasInHugeMethod extends TestBase implements Opcodes {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ // With 1311 the TestClass main method exceeds class file method size limit
+ static int repeat = 1310;
+
+ private static final String EXPECTED_OUTPUT;
+
+ static {
+ List<String> lines = new ArrayList<>();
+ for (int i = 0; i < repeat; i++) {
+ lines.add("1");
+ lines.add("2");
+ lines.add("3");
+ lines.add("4.0");
+ lines.add("5");
+ }
+ EXPECTED_OUTPUT = StringUtils.lines(lines);
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addProgramClasses(MyConsumer.class, MyTriConsumer.class)
+ .addProgramClassFileData(generateTestClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testDesugaring() throws Exception {
+ // Cf to cf desugaring fails generating too large method in class file.
+ if (parameters.isCfRuntime()) {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForDesugaring(
+ parameters,
+ (options) -> {
+ /* no options change */
+ },
+ configuration -> configuration == DesugarTestConfiguration.D8_CF)
+ .addProgramClasses(MyConsumer.class, MyTriConsumer.class)
+ .addProgramClassFileData(generateTestClass())
+ .run(parameters.getRuntime(), TestClass.class));
+ } else {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForDesugaring(
+ parameters,
+ (options) -> {
+ /* no options change */
+ },
+ configuration -> configuration == DesugarTestConfiguration.D8_CF_D8_DEX)
+ .addProgramClasses(MyConsumer.class, MyTriConsumer.class)
+ .addProgramClassFileData(generateTestClass())
+ .run(parameters.getRuntime(), TestClass.class));
+ }
+
+ // Desugaring directly to DEX works with large methods.
+ testForDesugaring(
+ parameters,
+ (options) -> {
+ /* no options change */
+ },
+ configuration ->
+ configuration != DesugarTestConfiguration.D8_CF
+ && configuration != DesugarTestConfiguration.D8_CF_D8_DEX)
+ .addProgramClasses(MyConsumer.class, MyTriConsumer.class)
+ .addProgramClassFileData(generateTestClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MyConsumer.class, MyTriConsumer.class)
+ .addProgramClassFileData(generateTestClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private byte[] generateTestClass() throws Exception {
+ return transformer(TestClass.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ // assert this is the placeholder.
+ for (int i = 0; i < repeat; i++) {
+ useStatelessLambda(visitor);
+ useStatefulLambdaWithOneCapture(visitor);
+ useStatefulLambdaWithThreeCaptures(visitor);
+ }
+ })
+ .setMaxStackHeight(MethodPredicate.onName("main"), 4)
+ .transform();
+ }
+
+ public interface MyConsumer<T> {
+ void create(T o);
+ }
+
+ public interface MyTriConsumer<T, U, V> {
+ void accept(T o1, U o2, V o3);
+ }
+
+ static class TestClass {
+
+ public static void greet() {
+ System.out.println("1");
+ }
+
+ public static void greet(MyConsumer<String> consumer) {
+ consumer.create("2");
+ }
+
+ public static void greetTri(long l, double d, String s) {
+ System.out.println(l);
+ System.out.println(d);
+ System.out.println(s);
+ }
+
+ public static void main(String[] args) throws Exception {
+ // Single static invoke instruction transformed.
+ greet();
+ // **** This code block is repeated "repeat" times in place of "greet()" ****:
+ // ((Runnable) TestClass::greet).run();
+ // greet(System.out::println);
+ // ((MyTriConsumer<Long, Double, String>) TestClass::greetTri).accept(3L, 4.0, "5");
+ }
+ }
+
+ private static void useStatelessLambda(MethodVisitor methodVisitor) {
+ methodVisitor.visitInvokeDynamicInsn(
+ "run",
+ "()Ljava/lang/Runnable;",
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/LambdaMetafactory",
+ "metafactory",
+ "(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),
+ Type.getType("()V"),
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "com/android/tools/r8/desugar/lambdas/LambdasInHugeMethod$TestClass",
+ "greet",
+ "()V",
+ false),
+ Type.getType("()V"));
+ methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/lang/Runnable", "run", "()V", true);
+ }
+
+ private static void useStatefulLambdaWithOneCapture(MethodVisitor methodVisitor) {
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/util/Objects",
+ "requireNonNull",
+ "(Ljava/lang/Object;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInvokeDynamicInsn(
+ "create",
+ "(Ljava/io/PrintStream;)Lcom/android/tools/r8/desugar/lambdas/LambdasInHugeMethod$MyConsumer;",
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/LambdaMetafactory",
+ "metafactory",
+ "(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),
+ new Object[] {
+ Type.getType("(Ljava/lang/Object;)V"),
+ new Handle(
+ Opcodes.H_INVOKEVIRTUAL,
+ "java/io/PrintStream",
+ "println",
+ "(Ljava/lang/String;)V",
+ false),
+ Type.getType("(Ljava/lang/String;)V")
+ });
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/desugar/lambdas/LambdasInHugeMethod$TestClass",
+ "greet",
+ "(Lcom/android/tools/r8/desugar/lambdas/LambdasInHugeMethod$MyConsumer;)V",
+ false);
+ methodVisitor.visitInvokeDynamicInsn(
+ "accept",
+ "()Lcom/android/tools/r8/desugar/lambdas/LambdasInHugeMethod$MyTriConsumer;",
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/LambdaMetafactory",
+ "metafactory",
+ "(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),
+ new Object[] {
+ Type.getType("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V"),
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "com/android/tools/r8/desugar/lambdas/LambdasInHugeMethod$TestClass",
+ "greetTri",
+ "(JDLjava/lang/String;)V",
+ false),
+ Type.getType("(Ljava/lang/Long;Ljava/lang/Double;Ljava/lang/String;)V")
+ });
+ }
+
+ private static void useStatefulLambdaWithThreeCaptures(MethodVisitor methodVisitor) {
+ methodVisitor.visitLdcInsn(3L);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
+ methodVisitor.visitLdcInsn(Double.parseDouble("4.0"));
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
+ methodVisitor.visitLdcInsn("5");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE,
+ "com/android/tools/r8/desugar/lambdas/LambdasInHugeMethod$MyTriConsumer",
+ "accept",
+ "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V",
+ true);
+ }
+}