Split out reachable library methods and report as library edges.

Bug: 120959039
Change-Id: I917ae6aed746ab3cfa7b829205e3372485aebc46
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index 214d103..e028a0b 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
@@ -72,7 +72,8 @@
       case ReferencedInAnnotation:
         return "referenced in annotation";
       case IsLibraryMethod:
-        return "defined in library";
+        // TODO(b/120959039): It would be good to also surface the defining library type.
+        return "defined in library method overridden by";
       case MethodHandleUseFrom:
         return "referenced by method handle";
       default:
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 35cd3d1..bbdd93f 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -68,6 +68,7 @@
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -177,6 +178,11 @@
    */
   private final Map<DexType, SetWithReason<DexEncodedMethod>> reachableVirtualMethods = Maps
       .newIdentityHashMap();
+
+  // TODO(b/139464956): Lazily compute library dependencies.
+  private final Map<DexType, Map<DexEncodedMethod, Set<DexType>>>
+      reachableVirtualMethodsFromLibraries = Maps.newIdentityHashMap();
+
   /**
    * Tracks the dependency between a method and the super-method it calls, if any. Used to make
    * super methods become live when they become reachable from a live sub-method.
@@ -848,12 +854,7 @@
       // runtime. Illegal dispatch situations and the corresponding exceptions are already handled
       // by the reachability logic.
       ScopedDexMethodSet seen = new ScopedDexMethodSet();
-      SetWithReason<DexEncodedMethod> reachableMethods =
-          reachableVirtualMethods.get(instantiatedType);
-      if (reachableMethods != null) {
-        transitionNonAbstractMethodsToLiveAndShadow(
-            reachableMethods.getItems(), instantiatedType, seen);
-      }
+      transitionReachableVirtualMethods(instantiatedType, seen);
       Collections.addAll(allInterfaces, clazz.interfaces.values);
 
       // The set now contains all virtual methods on the type and its supertype that are reachable.
@@ -895,6 +896,30 @@
     }
   }
 
+  private void transitionReachableVirtualMethods(DexType type, ScopedDexMethodSet seen) {
+    SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(type);
+    if (reachableMethods != null) {
+      transitionNonAbstractMethodsToLiveAndShadow(type, reachableMethods, seen);
+    }
+    Map<DexEncodedMethod, Set<DexType>> libraryMethods =
+        reachableVirtualMethodsFromLibraries.get(type);
+    if (libraryMethods == null) {
+      return;
+    }
+    for (Entry<DexEncodedMethod, Set<DexType>> entry : libraryMethods.entrySet()) {
+      DexEncodedMethod encodedMethod = entry.getKey();
+      if (seen.addMethod(encodedMethod)) {
+        // Abstract methods do shadow implementations but they cannot be live, as they have no
+        // code.
+        // TODO(b/120959039): The edge registration needs to be per interface.
+        KeepReason reason = KeepReason.isLibraryMethod(type, entry.getValue().iterator().next());
+        if (!encodedMethod.accessFlags.isAbstract()) {
+          markVirtualMethodAsLive(encodedMethod, reason);
+        }
+      }
+    }
+  }
+
   private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
     DexClass methodHolderClass = appView.definitionFor(method.holder);
     if (methodHolderClass != null && methodHolderClass.isInterface()) {
@@ -1265,11 +1290,7 @@
       // We only have to look at virtual methods here, as only those can actually be executed at
       // runtime. Illegal dispatch situations and the corresponding exceptions are already handled
       // by the reachability logic.
-      SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(type);
-      if (reachableMethods != null) {
-        transitionNonAbstractMethodsToLiveAndShadow(reachableMethods.getItems(), instantiatedType,
-            seen);
-      }
+      transitionReachableVirtualMethods(type, seen);
       Collections.addAll(interfaces, clazz.interfaces.values);
       type = clazz.superType;
     } while (type != null && !instantiatedTypes.contains(type));
@@ -1300,25 +1321,21 @@
       return;
     }
     assert clazz.accessFlags.isInterface();
-    SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(iface);
-    if (reachableMethods != null) {
-      transitionNonAbstractMethodsToLiveAndShadow(
-          reachableMethods.getItems(), instantiatedType, seen.newNestedScope());
-    }
+    transitionReachableVirtualMethods(iface, seen.newNestedScope());
     for (DexType subInterface : clazz.interfaces.values) {
       transitionDefaultMethodsForInstantiatedClass(subInterface, instantiatedType, seen);
     }
   }
 
-  private void transitionNonAbstractMethodsToLiveAndShadow(Iterable<DexEncodedMethod> reachable,
-      DexType instantiatedType, ScopedDexMethodSet seen) {
-    for (DexEncodedMethod encodedMethod : reachable) {
+  private void transitionNonAbstractMethodsToLiveAndShadow(
+      DexType type, SetWithReason<DexEncodedMethod> reachable, ScopedDexMethodSet seen) {
+    for (DexEncodedMethod encodedMethod : reachable.getItems()) {
       if (seen.addMethod(encodedMethod)) {
         // Abstract methods do shadow implementations but they cannot be live, as they have no
         // code.
+        // TODO(b/120959039): The reasons need to be stored and then forwarded here!
         if (!encodedMethod.accessFlags.isAbstract()) {
-          markVirtualMethodAsLive(encodedMethod,
-              KeepReason.reachableFromLiveType(instantiatedType));
+          markVirtualMethodAsLive(encodedMethod, KeepReason.reachableFromLiveType(type));
         }
       }
     }
@@ -1564,7 +1581,7 @@
 
   private void markVirtualMethodAsReachable(
       DexMethod method, boolean interfaceInvoke, KeepReason reason) {
-    markVirtualMethodAsReachable(method, interfaceInvoke, reason, (x, y) -> true, null);
+    markVirtualMethodAsReachable(method, interfaceInvoke, reason, (x, y) -> true, null, false);
   }
 
   private void markVirtualMethodAsReachable(
@@ -1572,7 +1589,8 @@
       boolean interfaceInvoke,
       KeepReason reason,
       BiPredicate<DexProgramClass, DexEncodedMethod> possibleTargetsFilter,
-      Consumer<DexEncodedMethod> possibleTargetsConsumer) {
+      Consumer<DexEncodedMethod> possibleTargetsConsumer,
+      boolean isLibraryEnqueueing) {
     if (!virtualTargetsMarkedAsReachable.add(method)) {
       return;
     }
@@ -1625,12 +1643,23 @@
         continue;
       }
 
-      // TODO(b/120959039): The reachable.add test might be hiding other paths to the method.
-      SetWithReason<DexEncodedMethod> reachable =
-          reachableVirtualMethods.computeIfAbsent(
-              possibleTarget.holder, ignore -> newSetWithoutReasonReporter());
-      if (!reachable.add(encodedPossibleTarget, reason)) {
-        continue;
+      if (isLibraryEnqueueing) {
+        Map<DexEncodedMethod, Set<DexType>> entry =
+            reachableVirtualMethodsFromLibraries.computeIfAbsent(
+                possibleTarget.holder, ignore -> Maps.newIdentityHashMap());
+        Set<DexType> libraryTypes = entry.get(encodedPossibleTarget);
+        if (libraryTypes != null) {
+          libraryTypes.add(method.holder);
+          continue;
+        }
+        entry.put(encodedPossibleTarget, SetUtils.newIdentityHashSet(method.holder, 2));
+      } else {
+        SetWithReason<DexEncodedMethod> reachable =
+            reachableVirtualMethods.computeIfAbsent(
+                possibleTarget.holder, ignore -> newSetWithoutReasonReporter());
+        if (!reachable.add(encodedPossibleTarget, reason)) {
+          continue;
+        }
       }
 
       // Abstract methods cannot be live.
@@ -2036,14 +2065,17 @@
       Log.verbose(
           getClass(), "Marking all methods of library class `%s` as reachable.", clazz.type);
     }
+    // TODO(b/139464956, b/124480748): Remove this 'reason'. Lazy load libraries and no reporting.
+    KeepReason reason = KeepReason.isLibraryMethod(clazz.type, clazz.type);
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
-      markMethodAsTargeted(encodedMethod, KeepReason.isLibraryMethod());
+      markMethodAsTargeted(encodedMethod, reason);
       markVirtualMethodAsReachable(
           encodedMethod.method,
           clazz.isInterface(),
-          KeepReason.isLibraryMethod(),
+          reason,
           this::shouldMarkLibraryMethodOverrideAsReachable,
-          DexEncodedMethod::setLibraryMethodOverride);
+          DexEncodedMethod::setLibraryMethodOverride,
+          true);
     }
   }
 
@@ -2715,10 +2747,15 @@
     registerEdge(getAnnotationGraphNode(annotation.annotation.type), reason);
   }
 
+  private boolean isNonProgramClass(DexType type) {
+    DexClass clazz = appView.definitionFor(type);
+    return clazz == null || clazz.isNotProgramClass();
+  }
+
   private void registerMethod(DexEncodedMethod method, KeepReason reason) {
-    if (reason.edgeKind() == EdgeKind.IsLibraryMethod) {
+    if (reason.edgeKind() == EdgeKind.IsLibraryMethod && isNonProgramClass(method.method.holder)) {
       // Don't report edges to actual library methods.
-      // TODO(b/120959039): Make sure we do have edges to methods overwriting library methods!
+      // TODO(b/120959039): This should be dead code once no library classes are ever enqueued.
       return;
     }
     assert getSourceNode(reason) != null;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 673326c..3dfec87 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -67,8 +67,8 @@
     return new InvokedFromLambdaCreatedIn(method);
   }
 
-  public static KeepReason isLibraryMethod() {
-    return IsLibraryMethod.getInstance();
+  public static KeepReason isLibraryMethod(DexType implementer, DexType libraryType) {
+    return new IsLibraryMethod(implementer, libraryType);
   }
 
   public static KeepReason fieldReferencedIn(DexEncodedMethod method) {
@@ -350,14 +350,14 @@
     }
   }
 
-  private static class IsLibraryMethod extends KeepReason {
+  public static class IsLibraryMethod extends KeepReason {
 
-    private static final IsLibraryMethod instance = new IsLibraryMethod();
+    private final DexType implementer;
+    private final DexType libraryType;
 
-    private IsLibraryMethod() {}
-
-    public static IsLibraryMethod getInstance() {
-      return instance;
+    private IsLibraryMethod(DexType implementer, DexType libraryType) {
+      this.implementer = implementer;
+      this.libraryType = libraryType;
     }
 
     @Override
@@ -367,7 +367,7 @@
 
     @Override
     public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return null;
+      return enqueuer.getClassGraphNode(implementer);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 185cfbd..190b744 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.utils;
 
 import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.Set;
 
 public class SetUtils {
@@ -20,4 +22,14 @@
     c.forEach(result::add);
     return result;
   }
+
+  public static <T> Set<T> newIdentityHashSet(int capacity) {
+    return Collections.newSetFromMap(new IdentityHashMap<>(capacity));
+  }
+
+  public static <T> Set<T> newIdentityHashSet(T element, int capacity) {
+    Set<T> result = newIdentityHashSet(capacity);
+    result.add(element);
+    return result;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByLibraryMethod.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByLibraryMethod.java
new file mode 100644
index 0000000..0bab978
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByLibraryMethod.java
@@ -0,0 +1,94 @@
+// 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 com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import java.util.ArrayList;
+import java.util.Collection;
+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 KeptByLibraryMethod extends TestBase {
+
+  // Library
+
+  public abstract static class Drawable {
+    abstract void draw();
+  }
+
+  public static class Scene {
+    private Collection<Drawable> drawables = new ArrayList<>();
+
+    public void register(Drawable drawable) {
+      drawables.add(drawable);
+    }
+
+    public void drawScene() {
+      for (Drawable drawable : drawables) {
+        drawable.draw();
+      }
+    }
+  }
+
+  // Program
+
+  public static class MyShape extends Drawable {
+
+    @Override
+    void draw() {
+      System.out.println("MyShape was drawn!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Scene scene = new Scene();
+      scene.register(new MyShape());
+      scene.drawScene();
+    }
+  }
+
+  // Test runner
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public KeptByLibraryMethod(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    GraphInspector inspector =
+        testForR8(parameters.getBackend())
+            .enableGraphInspector()
+            .addKeepMainRule(Main.class)
+            .addLibraryClasses(Scene.class, Drawable.class)
+            .addLibraryFiles(TestBase.runtimeJar(parameters.getBackend()))
+            .addProgramClasses(Main.class, MyShape.class)
+            .compile()
+            // The use of classpath classes is unsupported for DEX runtimes currently.
+            .addRunClasspathClasses(Scene.class, Drawable.class)
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("MyShape was drawn!")
+            .graphInspector();
+
+    inspector
+        .method(Reference.methodFromMethod(MyShape.class.getDeclaredMethod("draw")))
+        .assertPresent()
+        .assertKeptByLibraryMethod(inspector.clazz(Reference.classFromClass(MyShape.class)));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 4729766..2ada844 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -46,6 +46,8 @@
     public static final EdgeKindPredicate invokedFrom = new EdgeKindPredicate(EdgeKind.InvokedFrom);
     public static final EdgeKindPredicate reflectedFrom =
         new EdgeKindPredicate(EdgeKind.ReflectiveUseFrom);
+    public static final EdgeKindPredicate isLibraryMethod =
+        new EdgeKindPredicate(EdgeKind.IsLibraryMethod);
 
     private final EdgeKind edgeKind;
 
@@ -131,6 +133,8 @@
 
     public abstract boolean isKeptBy(QueryNode node);
 
+    public abstract boolean isKeptByLibraryMethod(QueryNode node);
+
     public abstract boolean isSatisfiedBy(QueryNode node);
 
     abstract String getNodeDescription();
@@ -226,6 +230,17 @@
       return this;
     }
 
+    public QueryNode assertKeptByLibraryMethod(QueryNode node) {
+      assertTrue(
+          "Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
+      assertTrue(
+          errorMessage(
+              "kept by library method on " + node.getNodeDescription(),
+              "was not kept by a library method"),
+          isKeptByLibraryMethod(node));
+      return this;
+    }
+
     public abstract String getKeptGraphString();
   }
 
@@ -294,6 +309,12 @@
     }
 
     @Override
+    public boolean isKeptByLibraryMethod(QueryNode node) {
+      fail("Invalid call to isKeptByLibrary on " + getNodeDescription());
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean isSatisfiedBy(QueryNode node) {
       fail("Invalid call to isTriggeredBy on " + getNodeDescription());
       throw new Unreachable();
@@ -389,6 +410,20 @@
     }
 
     @Override
+    public boolean isKeptByLibraryMethod(QueryNode node) {
+      assert graphNode instanceof MethodGraphNode;
+      if (!(node instanceof QueryNodeImpl)) {
+        return false;
+      }
+      QueryNodeImpl impl = (QueryNodeImpl) node;
+      return filterSources(
+              (source, infos) ->
+                  impl.graphNode == source && EdgeKindPredicate.isLibraryMethod.test(infos))
+          .findFirst()
+          .isPresent();
+    }
+
+    @Override
     public boolean isSatisfiedBy(QueryNode node) {
       assertTrue(
           "Invalid call to isTriggeredBy on non-keep rule node: " + graphNode,