Report each reason for targeting a method.

Bug: 120959039
Change-Id: I736c472146951f64193482194d5df5424d1f7b0f
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 e77f932..1eb64de 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -284,10 +284,9 @@
    */
   private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet();
 
-  /**
-   * A cache for DexMethod that have been marked reachable.
-   */
-  private final Set<DexMethod> virtualTargetsMarkedAsReachable = Sets.newIdentityHashSet();
+  /** A cache for DexMethod that have been marked reachable. */
+  private final Map<DexMethod, DexEncodedMethod> virtualTargetsMarkedAsReachable =
+      Maps.newIdentityHashMap();
 
   /**
    * A set of references we have reported missing to dedupe warnings.
@@ -596,9 +595,14 @@
 
     private final DexEncodedMethod currentMethod;
 
-    private UseRegistry(DexItemFactory factory, DexEncodedMethod currentMethod) {
+    private UseRegistry(DexItemFactory factory, DexProgramClass holder, DexEncodedMethod method) {
       super(factory);
-      this.currentMethod = currentMethod;
+      assert holder.type == method.method.holder;
+      this.currentMethod = method;
+    }
+
+    private KeepReasonWitness reportClassReferenced(DexProgramClass referencedClass) {
+      return graphReporter.reportClassReferencedFrom(referencedClass, currentMethod);
     }
 
     @Override
@@ -744,10 +748,8 @@
       // the field as live, if the holder is an interface.
       if (appView.options().enableUnusedInterfaceRemoval) {
         if (encodedField.field != field) {
-          markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, currentMethod));
-          markTypeAsLive(
-              encodedField.field.type,
-              type -> graphReporter.reportClassReferencedFrom(type, currentMethod));
+          markTypeAsLive(clazz, reportClassReferenced(clazz));
+          markTypeAsLive(encodedField.field.type, this::reportClassReferenced);
         }
       }
 
@@ -784,10 +786,8 @@
       // the field as live, if the holder is an interface.
       if (appView.options().enableUnusedInterfaceRemoval) {
         if (encodedField.field != field) {
-          markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, currentMethod));
-          markTypeAsLive(
-              encodedField.field.type,
-              type -> graphReporter.reportClassReferencedFrom(type, currentMethod));
+          markTypeAsLive(clazz, reportClassReferenced(clazz));
+          markTypeAsLive(encodedField.field.type, this::reportClassReferenced);
         }
       }
 
@@ -923,7 +923,7 @@
 
     @Override
     public boolean registerTypeReference(DexType type) {
-      markTypeAsLive(type, clazz -> graphReporter.reportClassReferencedFrom(clazz, currentMethod));
+      markTypeAsLive(type, this::reportClassReferenced);
       return true;
     }
 
@@ -1056,8 +1056,7 @@
           markClassAsInstantiatedWithCompatRule(baseClass);
         } else {
           // This also handles reporting of missing classes.
-          markTypeAsLive(
-              baseType, clazz -> graphReporter.reportClassReferencedFrom(clazz, currentMethod));
+          markTypeAsLive(baseType, this::reportClassReferenced);
         }
         return true;
       }
@@ -1847,9 +1846,6 @@
       boolean interfaceInvoke,
       KeepReason reason,
       BiPredicate<DexProgramClass, DexEncodedMethod> possibleTargetsFilter) {
-    if (!virtualTargetsMarkedAsReachable.add(method)) {
-      return;
-    }
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Marking virtual method `%s` as reachable.", method);
     }
@@ -1867,8 +1863,13 @@
       return;
     }
 
-    DexEncodedMethod resolutionTarget =
-        findAndMarkResolutionTarget(method, interfaceInvoke, reason);
+    DexEncodedMethod resolutionTarget = virtualTargetsMarkedAsReachable.get(method);
+    if (resolutionTarget != null) {
+      registerMethod(resolutionTarget, reason);
+      return;
+    }
+    resolutionTarget = findAndMarkResolutionTarget(method, interfaceInvoke, reason);
+    virtualTargetsMarkedAsReachable.put(method, resolutionTarget);
     if (resolutionTarget == null || !resolutionTarget.isValidVirtualTarget(options)) {
       // There is no valid resolution, so any call will lead to a runtime exception.
       return;
@@ -2514,7 +2515,7 @@
       method.parameterAnnotationsList.forEachAnnotation(
           annotation -> processAnnotation(method, annotation));
     }
-    method.registerCodeReferences(new UseRegistry(options.itemFactory, method));
+    method.registerCodeReferences(new UseRegistry(options.itemFactory, clazz, method));
 
     // Add all dependent members to the workqueue.
     enqueueRootItems(rootSet.getDependentItems(method));
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
new file mode 100644
index 0000000..8a67d39
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2019, 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.shaking.keptgraph;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+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 KeptByTwoMethods extends TestBase {
+
+  @NeverMerge
+  public static class A {
+
+    @NeverInline
+    void foo() {
+      System.out.println("A.foo!");
+    }
+  }
+
+  @NeverMerge
+  public static class B extends A {
+    // Intermediate to A.
+  }
+
+  public static class TestClass {
+
+    @NeverInline
+    static void bar(B b) {
+      b.foo();
+    }
+
+    @NeverInline
+    static void baz(B b) {
+      b.foo();
+    }
+
+    public static void main(String[] args) {
+      bar(new B());
+      baz(new B());
+    }
+  }
+
+  private static final String EXPECTED = StringUtils.lines("A.foo!", "A.foo!");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public KeptByTwoMethods(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    GraphInspector inspector =
+        testForR8(parameters.getBackend())
+            .enableGraphInspector()
+            .enableMergeAnnotations()
+            .enableInliningAnnotations()
+            .addKeepMainRule(TestClass.class)
+            .addProgramClasses(TestClass.class, A.class, B.class)
+            .run(parameters.getRuntime(), TestClass.class)
+            .assertSuccessWithOutput(EXPECTED)
+            .graphInspector();
+
+    MethodReference barRef = methodFromMethod(TestClass.class.getDeclaredMethod("bar", B.class));
+    inspector.method(barRef).assertPresent();
+
+    MethodReference bazRef = methodFromMethod(TestClass.class.getDeclaredMethod("baz", B.class));
+    inspector.method(bazRef).assertPresent();
+
+    QueryNode foo =
+        inspector.method(methodFromMethod(A.class.getDeclaredMethod("foo"))).assertPresent();
+
+    foo.assertInvokedFrom(barRef);
+    foo.assertInvokedFrom(bazRef);
+  }
+}