Fix lambda desugaring using nest based access In Jdk 17 and ownwards, javac does not generate anymore a bridge for lambda callsite using nest based access desugaring. This CL adds the lambda callsite analysis in the NestBasedAccessDesugaring to generate bridges for such methods. Bug: b/236695789 Change-Id: I2537b03d15efe0ce5dfd95db524d444785d4dd4f
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java index ac5addd..e348356 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -690,6 +690,8 @@ assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode(); assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode(); ProgramMethod newMethod = new ProgramMethod(implMethodHolder, replacement); + // TODO(b/236937595): Investigate why processing is needed here only in desugared library + // compilation. if (appView.options().isDesugaredLibraryCompilation()) { assert appView.options().isGeneratingClassFiles(); needsProcessingConsumer.accept(newMethod); @@ -778,6 +780,8 @@ assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode(); assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode(); ProgramMethod newMethod = new ProgramMethod(implMethodHolder, replacement); + // TODO(b/236937595): Investigate why processing is needed here only in desugared library + // compilation. if (appView.options().isDesugaredLibraryCompilation()) { assert appView.options().isGeneratingClassFiles(); needsProcessingConsumer.accept(newMethod); @@ -846,10 +850,7 @@ .disableAndroidApiLevelCheck() .build()); accessorClass.addDirectMethod(accessorMethod.getDefinition()); - if (appView.options().isDesugaredLibraryCompilation() - || appView.options().isGeneratingDex()) { - needsProcessingConsumer.accept(accessorMethod); - } + needsProcessingConsumer.accept(accessorMethod); return accessorMethod; } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java index 4da9973..37451f0 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.cf.code.CfFieldInstruction; import com.android.tools.r8.cf.code.CfInstruction; import com.android.tools.r8.cf.code.CfInvoke; +import com.android.tools.r8.cf.code.CfInvokeDynamic; import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; @@ -34,6 +35,7 @@ import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.FreshLocalProvider; +import com.android.tools.r8.ir.desugar.LambdaDescriptor; import com.android.tools.r8.ir.desugar.LocalStackAllocator; import com.android.tools.r8.ir.desugar.ProgramAdditions; import com.android.tools.r8.utils.BooleanUtils; @@ -146,6 +148,20 @@ if (needsDesugaring(invokedMethod, method)) { prepareDesugarMethodInstruction(invokedMethod, method, programAdditions); } + } else if (instruction.isInvokeDynamic()) { + // Starting from Java 17, lambda can use nest based access. We need to generate + // bridges for the targeted lambda method. + CfInvokeDynamic cfInvokeDynamic = instruction.asInvokeDynamic(); + LambdaDescriptor lambdaDescriptor = + LambdaDescriptor.tryInfer( + cfInvokeDynamic.getCallSite(), appView.appInfoForDesugaring(), method); + if (lambdaDescriptor != null) { + DexMember<?, ?> member = lambdaDescriptor.implHandle.member; + if (needsDesugaring(member, method)) { + assert member.isDexMethod(); + prepareDesugarMethodInstruction(member.asDexMethod(), method, programAdditions); + } + } } }); }
diff --git a/src/test/examplesJava17/nest/NestLambda.java b/src/test/examplesJava17/nest/NestLambda.java new file mode 100644 index 0000000..2c690b5 --- /dev/null +++ b/src/test/examplesJava17/nest/NestLambda.java
@@ -0,0 +1,33 @@ +// Copyright (c) 2022, 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 nest; + +import java.util.function.Consumer; + +public class NestLambda { + + private void print(Object o) { + System.out.println("printed: " + o); + } + + Inner getInner() { + return new Inner(); + } + + class Inner { + + void exec(Consumer<Object> consumer) { + consumer.accept("inner"); + } + + void execLambda() { + exec(NestLambda.this::print); + } + } + + public static void main(String[] args) { + new NestLambda().getInner().execLambda(); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java new file mode 100644 index 0000000..94fbe71 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java
@@ -0,0 +1,81 @@ +// Copyright (c) 2022, 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.nestaccesscontrol; + +import static com.android.tools.r8.utils.AndroidApiLevel.B; +import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.utils.AndroidApiLevel; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class NestLambdaJava17Test extends TestBase { + + public NestLambdaJava17Test(TestParameters parameters) { + this.parameters = parameters; + } + + private static final Path JDK17_JAR = + Paths.get(ToolHelper.TESTS_BUILD_DIR, "examplesJava17").resolve("nest" + JAR_EXTENSION); + private static final String MAIN = "nest.NestLambda"; + private static final String EXPECTED_RESULT = "printed: inner"; + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters() + .withCfRuntimesStartingFromIncluding(CfVm.JDK17) + // The test requires the java.util.function. package. + .withDexRuntimesStartingFromIncluding(Version.V7_0_0) + .withApiLevel(AndroidApiLevel.N) + .enableApiLevelsForCf() + .build(); + } + + @Test + public void testReference() throws Exception { + Assume.assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramFiles(JDK17_JAR) + .run(parameters.getRuntime(), MAIN) + .assertSuccessWithOutputLines(EXPECTED_RESULT); + } + + @Test + public void testJavaD8() throws Exception { + testForDesugaring(parameters) + .addProgramFiles(JDK17_JAR) + .run(parameters.getRuntime(), MAIN) + .assertSuccessWithOutputLines(EXPECTED_RESULT); + } + + @Test + public void testR8() throws Exception { + Assume.assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().equals(B)); + testForR8(parameters.getBackend()) + .addProgramFiles(JDK17_JAR) + .applyIf( + parameters.isCfRuntime(), + // Alternatively we need to pass Jdk17 as library. + b -> b.addKeepRules("-dontwarn java.lang.invoke.StringConcatFactory")) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(MAIN) + .run(parameters.getRuntime(), MAIN) + .assertSuccessWithOutputLines(EXPECTED_RESULT); + } +}