Insert bridges for kept interface methods after enqueueing.

Change-Id: Ide12b2b77a52fdd64b51a2a9fcd7306b5d6b2e7a
diff --git a/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
index 773db00..1c99cc6 100644
--- a/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
+++ b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import java.util.function.Consumer;
 
 public abstract class DelayedRootSetActionItem {
@@ -18,24 +18,22 @@
   }
 
   public static class InterfaceMethodSyntheticBridgeAction extends DelayedRootSetActionItem {
-    private final DexEncodedMethod methodToKeep;
-    private final DexEncodedMethod singleTarget;
+    private final ProgramMethod methodToKeep;
+    private final ProgramMethod singleTarget;
     private final Consumer<RootSetBuilder> action;
 
     InterfaceMethodSyntheticBridgeAction(
-        DexEncodedMethod methodToKeep,
-        DexEncodedMethod singleTarget,
-        Consumer<RootSetBuilder> action) {
+        ProgramMethod methodToKeep, ProgramMethod singleTarget, Consumer<RootSetBuilder> action) {
       this.methodToKeep = methodToKeep;
       this.singleTarget = singleTarget;
       this.action = action;
     }
 
-    public DexEncodedMethod getMethodToKeep() {
+    public ProgramMethod getMethodToKeep() {
       return methodToKeep;
     }
 
-    public DexEncodedMethod getSingleTarget() {
+    public ProgramMethod getSingleTarget() {
       return singleTarget;
     }
 
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 ac1dea1..1b76214 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2263,6 +2263,13 @@
         (field, info) -> field != info.getField() || info == MISSING_FIELD_ACCESS_INFO);
     assert fieldAccessInfoCollection.verifyMappingIsOneToOne();
 
+    for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
+      appView.appInfo().invalidateTypeCacheFor(bridge.holder.type);
+      bridge.holder.appendVirtualMethod(bridge.method);
+      targetedMethods.add(bridge.method, graphReporter.fakeReportShouldNotBeUsed());
+      liveMethods.add(bridge.holder, bridge.method, graphReporter.fakeReportShouldNotBeUsed());
+    }
+
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
             appInfo,
@@ -2460,25 +2467,31 @@
     return builder.buildConsequentRootSet();
   }
 
+  private Map<DexMethod, ProgramMethod> syntheticInterfaceMethodBridges = new IdentityHashMap<>();
+
   private void handleInterfaceMethodSyntheticBridgeAction(
       InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) {
-    if (rootSet.noShrinking.containsKey(action.getSingleTarget().method)) {
+    ProgramMethod methodToKeep = action.getMethodToKeep();
+    ProgramMethod singleTarget = action.getSingleTarget();
+    if (rootSet.noShrinking.containsKey(singleTarget.method.method)) {
       return;
     }
-    DexEncodedMethod methodToKeep = action.getMethodToKeep();
-    DexEncodedMethod singleTarget = action.getSingleTarget();
-    DexClass clazz = getProgramClassOrNull(methodToKeep.method.holder);
     if (methodToKeep != singleTarget) {
-      // Insert a bridge method.
-      if (appView.definitionFor(methodToKeep.method) == null) {
-        clazz.appendVirtualMethod(methodToKeep);
-        if (singleTarget.isLibraryMethodOverride().isTrue()) {
-          methodToKeep.setLibraryMethodOverride(OptionalBool.TRUE);
+      assert null == methodToKeep.holder.lookupMethod(methodToKeep.method.method);
+      ProgramMethod old =
+          syntheticInterfaceMethodBridges.put(methodToKeep.method.method, methodToKeep);
+      if (old == null) {
+        if (singleTarget.method.isLibraryMethodOverride().isTrue()) {
+          methodToKeep.method.setLibraryMethodOverride(OptionalBool.TRUE);
         }
-        appView.appInfo().invalidateTypeCacheFor(methodToKeep.method.holder);
-        // The addition of a bridge method can lead to a change of resolution, thus the cached
-        // resolution targets are invalid.
-        virtualTargetsMarkedAsReachable.remove(methodToKeep.method);
+        assert singleTarget.holder.isInterface();
+        markVirtualMethodAsReachable(
+            singleTarget.method.method,
+            singleTarget.holder.isInterface(),
+            null,
+            graphReporter.fakeReportShouldNotBeUsed());
+        enqueueMarkMethodLiveAction(
+            singleTarget.holder, singleTarget.method, graphReporter.fakeReportShouldNotBeUsed());
       }
     }
     action.getAction().accept(builder);
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index cf820bc..c172b52 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -67,6 +67,10 @@
     }
   }
 
+  public KeepReasonWitness fakeReportShouldNotBeUsed() {
+    return KeepReasonWitness.INSTANCE;
+  }
+
   public boolean verifyRootedPath(DexProgramClass liveType) {
     assert verificationGraphConsumer != null;
     ClassGraphNode node = getClassGraphNode(liveType.type);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index b842680..8555b93 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -23,7 +23,8 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
@@ -457,7 +458,9 @@
     }
     // TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
     //  fullmode.
-    if (clazz.isProgramClass()) {
+    if (clazz.isProgramClass()
+        && rule.isProguardKeepRule()
+        && !rule.asProguardKeepRule().getModifiers().allowsShrinking) {
       new SynthesizeMissingInterfaceMethodsForMemberRules(
               clazz.asProgramClass(), memberKeepRules, rule, preconditionSupplier, ifRule)
           .run();
@@ -484,6 +487,8 @@
         ProguardConfigurationRule context,
         Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
         ProguardIfRule ifRule) {
+      assert context.isProguardKeepRule();
+      assert !context.asProguardKeepRule().getModifiers().allowsShrinking;
       this.originalClazz = originalClazz;
       this.memberKeepRules = memberKeepRules;
       this.context = context;
@@ -525,49 +530,46 @@
     }
 
     private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
-      boolean shouldKeepMethod =
-          context.isProguardKeepRule()
-              && !context.asProguardKeepRule().getModifiers().allowsShrinking;
-      if (!shouldKeepMethod) {
+      SingleResolutionResult resolutionResult =
+          appView.appInfo().resolveMethod(originalClazz, method.method).asSingleResolution();
+      if (resolutionResult == null || !resolutionResult.isVirtualTarget()) {
         return;
       }
-      ResolutionResult resolutionResult =
-          appView.appInfo().resolveMethod(originalClazz, method.method);
-      if (!resolutionResult.isVirtualTarget() || !resolutionResult.isSingleResolution()) {
+      if (resolutionResult.getResolvedHolder() == originalClazz
+          || resolutionResult.getResolvedHolder().isNotProgramClass()) {
         return;
       }
-      DexEncodedMethod methodToKeep = resolutionResult.getSingleTarget();
-      if (methodToKeep.method.holder == originalClazz.type) {
-        return;
-      }
-      DexClass holder = appView.definitionFor(methodToKeep.method.holder);
-      if (holder.isNotProgramClass()) {
-        return;
-      }
-      if (!holder.isInterface()) {
+      if (!resolutionResult.getResolvedHolder().isInterface()) {
         // TODO(b/143643942): For fullmode, this check should probably be removed.
         return;
       }
-      if (canInsertForwardingMethod(originalClazz, methodToKeep)) {
-        methodToKeep = methodToKeep.toForwardingMethod(originalClazz, appView);
-      }
-      final DexEncodedMethod finalKeepMethod = methodToKeep;
+      ProgramMethod resolutionMethod =
+          new ProgramMethod(
+              resolutionResult.getResolvedHolder().asProgramClass(),
+              resolutionResult.getResolvedMethod());
+      ProgramMethod methodToKeep =
+          canInsertForwardingMethod(originalClazz, resolutionMethod.method)
+              ? new ProgramMethod(
+                  originalClazz, resolutionMethod.method.toForwardingMethod(originalClazz, appView))
+              : resolutionMethod;
+
       delayedRootSetActionItems.add(
           new InterfaceMethodSyntheticBridgeAction(
               methodToKeep,
-              resolutionResult.getSingleTarget(),
+              resolutionMethod,
               (rootSetBuilder) -> {
                 if (Log.ENABLED) {
                   Log.verbose(
                       getClass(),
                       "Marking method `%s` due to `%s { %s }`.",
-                      finalKeepMethod,
+                      methodToKeep,
                       context,
                       rule);
                 }
                 DexDefinition precondition =
-                    testAndGetPrecondition(finalKeepMethod, preconditionSupplier);
-                rootSetBuilder.addItemToSets(finalKeepMethod, context, rule, precondition, ifRule);
+                    testAndGetPrecondition(methodToKeep.method, preconditionSupplier);
+                rootSetBuilder.addItemToSets(
+                    methodToKeep.method, context, rule, precondition, ifRule);
               }));
     }
   }