Restrict lambda unpinning to moved methods.

Bug: 157700141
Change-Id: I4d74729a70fd5864a26597714af0fb38df52166d
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 0f050c5..c9e47c2 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
@@ -632,7 +632,7 @@
                             encodedMethod.getCode(),
                             true);
                     newMethod.copyMetadata(encodedMethod);
-                    rewriter.lensBuilder.move(encodedMethod.method, callTarget);
+                    rewriter.forcefullyMoveMethod(encodedMethod.method, callTarget);
 
                     DexEncodedMethod.setDebugInfoWithFakeThisParameter(
                         newMethod.getCode(), callTarget.getArity(), appView);
@@ -705,7 +705,7 @@
                             encodedMethod.getCode(),
                             true);
                     newMethod.copyMetadata(encodedMethod);
-                    rewriter.lensBuilder.move(encodedMethod.method, callTarget);
+                    rewriter.forcefullyMoveMethod(encodedMethod.method, callTarget);
                     return newMethod;
                   });
       return new ProgramMethod(implMethodHolder, replacement);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 7bb8d44..11cff1a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -46,6 +46,7 @@
 import com.google.common.base.Suppliers;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
@@ -77,7 +78,8 @@
 
   final DexString instanceFieldName;
 
-  final LambdaRewriterLens.Builder lensBuilder = LambdaRewriterLens.builder();
+  private final LambdaRewriterLens.Builder lensBuilder = LambdaRewriterLens.builder();
+  private final Set<DexMethod> forcefullyMovedMethods = Sets.newIdentityHashSet();
 
   // Maps call sites seen so far to inferred lambda descriptor. It is intended
   // to help avoid re-matching call sites we already seen. Note that same call
@@ -95,6 +97,15 @@
     this.instanceFieldName = appView.dexItemFactory().createString(LAMBDA_INSTANCE_FIELD_NAME);
   }
 
+  void forcefullyMoveMethod(DexMethod from, DexMethod to) {
+    lensBuilder.move(from, to);
+    forcefullyMovedMethods.add(from);
+  }
+
+  public Set<DexMethod> getForcefullyMovedMethods() {
+    return forcefullyMovedMethods;
+  }
+
   private void synthesizeAccessibilityBridgesForLambdaClassesD8(
       Collection<LambdaClass> lambdaClasses, IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index d1200b4..21bf839 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -263,8 +263,6 @@
    * Set of direct methods that are the immediate target of an invoke-dynamic.
    */
   private final Set<DexMethod> methodsTargetedByInvokeDynamic = Sets.newIdentityHashSet();
-  /** Set of direct lambda implementation methods that have been desugared, thus they may move. */
-  private final Set<DexMethod> desugaredLambdaImplementationMethods = Sets.newIdentityHashSet();
   /**
    * Set of virtual methods that are the immediate target of an invoke-direct.
    */
@@ -865,9 +863,6 @@
           classesWithSerializableLambdas.add(context.getHolder());
         }
       }
-      if (descriptor.delegatesToLambdaImplMethod()) {
-        desugaredLambdaImplementationMethods.add(descriptor.implHandle.asMethod());
-      }
     } else {
       markLambdaAsInstantiated(descriptor, context);
       transitionMethodsForInstantiatedLambda(descriptor);
@@ -3088,6 +3083,16 @@
                 liveMethods.add(accessor, graphReporter.fakeReportShouldNotBeUsed());
               }
             });
+    unpinLambdaMethods();
+  }
+
+  // TODO(b/157700141): Determine if this is the right way to allow modification of pinned lambdas.
+  private void unpinLambdaMethods() {
+    assert lambdaRewriter != null;
+    for (DexMethod method : lambdaRewriter.getForcefullyMovedMethods()) {
+      keepInfo.unsafeUnpinMethod(method);
+      rootSet.prune(method);
+    }
   }
 
   private boolean verifyMissingTypes() {
@@ -3296,7 +3301,6 @@
     } finally {
       timing.end();
     }
-    unpinLambdaMethods();
   }
 
   private long getNumberOfLiveItems() {
@@ -3392,17 +3396,6 @@
     action.getAction().accept(builder);
   }
 
-  // TODO(b/157700141): Determine if this is the right way to avoid modification of pinned lambdas.
-  private void unpinLambdaMethods() {
-    assert desugaredLambdaImplementationMethods.isEmpty()
-        || options.desugarState == DesugarState.ON;
-    for (DexMethod method : desugaredLambdaImplementationMethods) {
-      keepInfo.unsafeUnpinMethod(method);
-      rootSet.prune(method);
-    }
-    desugaredLambdaImplementationMethods.clear();
-  }
-
   void retainAnnotationForFinalTreeShaking(List<DexAnnotation> annotations) {
     assert mode.isInitialTreeShaking();
     if (annotationRemoverBuilder != null) {
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
index b2d5b54..5d84439 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
@@ -113,18 +113,17 @@
   public void testR8() throws Exception {
     try {
       testForR8(parameters.getBackend())
-          // TODO(b/158752316, b/157700141): Disable inlining to keep the synthetic lambda methods.
-          .addOptionsModification(options -> options.enableInlining = false)
           .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
           .setMinApi(parameters.getApiLevel())
-          .addKeepRules("-keep class * { *; }")
-          // Keeping the synthetic lambda methods is currently not supported - they are
-          // forcefully unpinned. The following rule has no effect. See b/b/157700141.
-          .addKeepRules("-keep class **.*$TestClass { synthetic *; }")
+          // Keep the synthesized inner classes.
+          .addKeepRules("-keep class **.*$TestClass$1")
+          .addKeepRules("-keep class **.*$TestClass$2")
+          // Keep the outer context: TestClass *and* the synthetic lambda methods.
+          .addKeepRules("-keep class **.*$TestClass { private synthetic void lambda$*(*); }")
           .addKeepAttributes("InnerClasses", "EnclosingMethod")
-          .compile()
-          .inspect(this::checkEnclosingMethod)
+          .addKeepMainRule(TestClass.class)
           .run(parameters.getRuntime(), TestClass.class)
+          .inspect(this::checkEnclosingMethod)
           .assertSuccessWithOutputLines(
               parameters.isCfRuntime() ? EXPECTED_JAVAC_RESULT : EXPECTED_DESUGARED_RESULT);
       assertFalse(parameters.isDexRuntime());
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
index 8c539d2..f272f26 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
@@ -36,9 +36,6 @@
           "Hello from inside lambda$test$0$DesugarLambdaWithLocalClass$TestClass",
           "Hello from inside lambda$testStatic$1");
 
-  private List<String> EXPECTED_DESUGARED_RESULT_R8_WITHOUT_INLINING =
-      ImmutableList.of("Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
-
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
@@ -113,22 +110,18 @@
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        // TODO(b/158752316, b/157700141): Disable inlining to keep the synthetic lambda methods.
-        .addOptionsModification(options -> options.enableInlining = false)
         .addInnerClasses(DesugarLambdaWithLocalClass.class)
         .setMinApi(parameters.getApiLevel())
-        .addKeepRules("-keep class * { *; }")
-        // Keeping the synthetic lambda methods is currently not supported - they are
-        // forcefully unpinned. The following rule has no effect. See b/b/157700141.
-        .addKeepRules("-keep class **.*$TestClass { synthetic *; }")
+        // Keep the synthesized inner classes.
+        .addKeepRules("-keep class **.*$TestClass$*MyConsumerImpl")
+        // Keep the outer context: TestClass *and* the synthetic lambda methods.
+        .addKeepRules("-keep class **.*$TestClass { private synthetic void lambda$*(*); }")
         .addKeepAttributes("InnerClasses", "EnclosingMethod")
-        .compile()
-        .inspect(this::checkEnclosingMethod)
+        .addKeepMainRule(TestClass.class)
         .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::checkEnclosingMethod)
         .assertSuccessWithOutputLines(
-            parameters.isCfRuntime()
-                ? EXPECTED_JAVAC_RESULT
-                : EXPECTED_DESUGARED_RESULT_R8_WITHOUT_INLINING);
+            parameters.isCfRuntime() ? EXPECTED_JAVAC_RESULT : EXPECTED_JAVAC_RESULT);
   }
 
   public interface MyConsumer<T> {