Record desugar dependencies for interface bridge methods.

Bug: 138988172
Change-Id: I40c87def18fca8c098b3dd593da1a013b0a0c625
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index b9542eb..86b2cb8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
 import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -144,6 +145,18 @@
     return app.definitionFor(type);
   }
 
+  public DexClass definitionForDesugarDependency(DexClass dependent, DexType type) {
+    if (dependent.type == type) {
+      return dependent;
+    }
+    DexClass definition = definitionFor(type);
+    if (definition != null && !definition.isLibraryClass() && dependent.isProgramClass()) {
+      InterfaceMethodRewriter.reportDependencyEdge(
+          dependent.asProgramClass(), definition, options());
+    }
+    return definition;
+  }
+
   @Override
   public DexProgramClass definitionForProgramType(DexType type) {
     return app.programDefinitionFor(type);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 6fb1c27..b54bcc9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -140,14 +140,6 @@
               : closestProgramSubClass);
     }
 
-    public void reportDependency(DexClass clazz, AppView<?> appView) {
-      // If the direct subclass is in the compilation unit, report its dependencies.
-      if (clazz != directSubClass && directSubClass.isProgramClass()) {
-        InterfaceMethodRewriter.reportDependencyEdge(
-            directSubClass.asProgramClass(), clazz, appView);
-      }
-    }
-
     public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
       rewriter.warnMissingInterface(closestProgramSubClass, closestProgramSubClass, missingType);
     }
@@ -168,11 +160,6 @@
     }
 
     @Override
-    public void reportDependency(DexClass clazz, AppView<?> appView) {
-      // Don't report dependencies in the library.
-    }
-
-    @Override
     public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
       // Ignore missing types in the library.
     }
@@ -401,7 +388,7 @@
     if (type == null || type == dexItemFactory.objectType) {
       return null;
     }
-    DexClass clazz = appView.definitionFor(type);
+    DexClass clazz = appView.appInfo().definitionForDesugarDependency(context.directSubClass, type);
     if (clazz == null) {
       context.reportMissingType(type, rewriter);
       return null;
@@ -419,7 +406,6 @@
     if (clazz.isLibraryClass()) {
       return ClassInfo.EMPTY;
     }
-    context.reportDependency(clazz, appView);
     return classInfo.computeIfAbsent(clazz, key -> visitClassInfoRaw(key, context));
   }
 
@@ -470,7 +456,6 @@
     if (iface.isLibraryClass() && ignoreLibraryInfo()) {
       return MethodSignatures.EMPTY;
     }
-    context.reportDependency(iface, appView);
     return interfaceInfo.computeIfAbsent(iface, key -> visitInterfaceInfoRaw(key, context));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 795755e..7f4c043 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -961,7 +961,7 @@
     InterfaceProcessor processor = new InterfaceProcessor(appView, this);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, true)) {
-        processor.process(clazz.asProgramClass(), graphLensBuilder);
+        processor.process(clazz, graphLensBuilder);
       }
     }
     for (Entry<DexLibraryClass, Set<DexProgramClass>> entry : requiredDispatchClasses.entrySet()) {
@@ -1114,8 +1114,8 @@
 
     // At this point we likely have a non-library type that may depend on default method information
     // from its interfaces and the dependency should be reported.
-    if (!definedInterface.isLibraryClass()) {
-      reportDependencyEdge(implementing, definedInterface, appView);
+    if (implementing.isProgramClass() && !definedInterface.isLibraryClass()) {
+      reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.options());
     }
 
     // Merge information from all superinterfaces.
@@ -1139,8 +1139,9 @@
   }
 
   public static void reportDependencyEdge(
-      DexClass dependent, DexClass dependency, AppView<?> appView) {
-    DesugarGraphConsumer consumer = appView.options().desugarGraphConsumer;
+      DexProgramClass dependent, DexClass dependency, InternalOptions options) {
+    assert !dependency.isLibraryClass();
+    DesugarGraphConsumer consumer = options.desugarGraphConsumer;
     if (consumer != null) {
       Origin dependentOrigin = dependent.getOrigin();
       Origin dependencyOrigin = dependency.getOrigin();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 787fbe1..9599834 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.BiMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -302,6 +303,12 @@
     return true;
   }
 
+  private DexClass definitionForDependency(DexType dependency, DexClass dependent) {
+    return dependent.isProgramClass()
+        ? appView.appInfo().definitionForDesugarDependency(dependent.asProgramClass(), dependency)
+        : appView.definitionFor(dependency);
+  }
+
   // Returns true if the given interface method must be kept on [iface] after moving its
   // implementation to the companion class of [iface]. This is always the case for non-bridge
   // methods. Bridge methods that does not override an implementation in a super-interface must
@@ -313,32 +320,33 @@
       }
     }
     if (method.accessFlags.isBridge()) {
-      Deque<DexType> worklist = new ArrayDeque<>();
+      Deque<Pair<DexClass, DexType>> worklist = new ArrayDeque<>();
       Set<DexType> seenBefore = new HashSet<>();
-      if (iface.superType != null) {
-        worklist.add(iface.superType);
-      }
-      Collections.addAll(worklist, iface.interfaces.values);
+      addSuperTypes(iface, worklist);
       while (!worklist.isEmpty()) {
-        DexType superType = worklist.pop();
-        if (!seenBefore.add(superType)) {
+        Pair<DexClass, DexType> item = worklist.pop();
+        DexClass clazz = definitionForDependency(item.getSecond(), item.getFirst());
+        if (clazz == null || !seenBefore.add(clazz.type)) {
           continue;
         }
-        DexClass clazz = appView.definitionFor(superType);
-        if (clazz != null) {
-          if (clazz.lookupVirtualMethod(method.method) != null) {
-            return false;
-          }
-          if (clazz.superType != null) {
-            worklist.add(clazz.superType);
-          }
-          Collections.addAll(worklist, clazz.interfaces.values);
+        if (clazz.lookupVirtualMethod(method.method) != null) {
+          return false;
         }
+        addSuperTypes(clazz, worklist);
       }
     }
     return true;
   }
 
+  private static void addSuperTypes(DexClass clazz, Deque<Pair<DexClass, DexType>> worklist) {
+    if (clazz.superType != null) {
+      worklist.add(new Pair<>(clazz, clazz.superType));
+    }
+    for (DexType iface : clazz.interfaces.values) {
+      worklist.add(new Pair<>(clazz, iface));
+    }
+  }
+
   private boolean isStaticMethod(DexEncodedMethod method) {
     if (method.accessFlags.isNative()) {
       throw new Unimplemented("Native interface methods are not yet supported.");
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
new file mode 100644
index 0000000..2111b7d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
@@ -0,0 +1,34 @@
+// 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.desugar.graph;
+
+import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.origin.Origin;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class DesugarGraphTestConsumer implements DesugarGraphConsumer {
+
+  private Map<Origin, Set<Origin>> edges = new HashMap<>();
+
+  public boolean contains(Origin dependency, Origin dependent) {
+    return edges.getOrDefault(dependency, Collections.emptySet()).contains(dependent);
+  }
+
+  public int totalEdgeCount() {
+    int count = 0;
+    for (Set<Origin> dependents : edges.values()) {
+      count += dependents.size();
+    }
+    return count;
+  }
+
+  @Override
+  public synchronized void accept(Origin dependent, Origin dependency) {
+    edges.computeIfAbsent(dependency, s -> new HashSet<>()).add(dependent);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java
new file mode 100644
index 0000000..af3c6a0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java
@@ -0,0 +1,28 @@
+// 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.desugar.graph;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import java.io.IOException;
+
+public class DesugarGraphUtils {
+
+  public static Origin addClassWithOrigin(Class<?> clazz, D8TestBuilder builder)
+      throws IOException {
+    Origin origin = makeOrigin(clazz.getTypeName());
+    builder.getBuilder().addClassProgramData(ToolHelper.getClassAsBytes(clazz), origin);
+    return origin;
+  }
+
+  private static Origin makeOrigin(String name) {
+    return new Origin(Origin.root()) {
+      @Override
+      public String part() {
+        return name;
+      }
+    };
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java
new file mode 100644
index 0000000..ec8fcd4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java
@@ -0,0 +1,98 @@
+// 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.desugar.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+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 InterfaceBridgeDependencyTest extends TestBase {
+
+  public interface I {
+    Object foo();
+  }
+
+  public interface J extends I {
+    // Covariant override of foo will result in a synthetic bridge method and J will depend on I.
+    default String foo() {
+      return "J::foo";
+    }
+  }
+
+  public interface K extends I {
+    // A simple override does not cause a bridge and there is no desugar dependencies for K.
+    default Object foo() {
+      return "K::foo";
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      // There are no instances of I, J or K, so just make sure that the code requires they exist.
+      System.out.println(I.class.getName());
+      System.out.println(J.class.getName());
+      System.out.println(K.class.getName());
+    }
+  }
+
+  // Test runner follows.
+
+  private static final String EXPECTED =
+      StringUtils.lines(I.class.getName(), J.class.getName(), K.class.getName());
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InterfaceBridgeDependencyTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClasses(I.class, J.class, K.class, TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+    } else {
+      D8TestBuilder builder = testForD8();
+      DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+      builder.getBuilder().setDesugarGraphConsumer(consumer);
+      Origin originI = DesugarGraphUtils.addClassWithOrigin(I.class, builder);
+      Origin originJ = DesugarGraphUtils.addClassWithOrigin(J.class, builder);
+      Origin originK = DesugarGraphUtils.addClassWithOrigin(K.class, builder);
+      builder
+          .setMinApi(parameters.getApiLevel())
+          .addProgramClasses(TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      // If API level indicates desugaring is needed check the edges are reported.
+      if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
+        assertTrue(consumer.contains(originI, originJ));
+        assertFalse(consumer.contains(originI, originK));
+        assertEquals(1, consumer.totalEdgeCount());
+      } else {
+        assertEquals(0, consumer.totalEdgeCount());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
index 64044e6..7f76cad 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
@@ -4,18 +4,14 @@
 package com.android.tools.r8.desugar.graph;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -26,7 +22,7 @@
 
   // The simplest program that gives rise to a dependency edge in the graph is an interface with
   // an implementing class. In this case, changes to the interface could require re-dexing the
-  // implementing class despite no derived changes happing to the classfile for the implementing
+  // implementing class despite no derived changes happening to the classfile for the implementing
   // class.
   //
   // For example, adding default method I.foo will trigger javac compilation of I and A to I.class
@@ -72,47 +68,24 @@
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutputLines("Hello World!");
     } else {
-      MyDesugarGraphConsumer consumer = new MyDesugarGraphConsumer();
-      Origin originI = makeOrigin("I");
-      Origin originA = makeOrigin("A");
-      testForD8()
-          .setMinApi(parameters.getApiLevel())
+      D8TestBuilder builder = testForD8();
+      DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+      builder.getBuilder().setDesugarGraphConsumer(consumer);
+      Origin originI = DesugarGraphUtils.addClassWithOrigin(I.class, builder);
+      Origin originA = DesugarGraphUtils.addClassWithOrigin(A.class, builder);
+      builder
           .addProgramClasses(TestClass.class)
-          .apply(
-              b ->
-                  b.getBuilder()
-                      .setDesugarGraphConsumer(consumer)
-                      .addClassProgramData(ToolHelper.getClassAsBytes(I.class), originI)
-                      .addClassProgramData(ToolHelper.getClassAsBytes(A.class), originA))
+          .setMinApi(parameters.getApiLevel())
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutputLines("Hello World!");
       // If API level indicates desugaring is needed check the edges are reported.
       if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
-        assertEquals(1, consumer.edges.size());
-        assertEquals(originI, consumer.edges.keySet().iterator().next());
-        assertEquals(originA, consumer.edges.values().iterator().next().iterator().next());
+        assertEquals(1, consumer.totalEdgeCount());
+        assertTrue(consumer.contains(originI, originA));
       } else {
-        assertEquals(0, consumer.edges.size());
+        assertEquals(0, consumer.totalEdgeCount());
       }
     }
   }
 
-  private Origin makeOrigin(String name) {
-    return new Origin(Origin.root()) {
-      @Override
-      public String part() {
-        return name;
-      }
-    };
-  }
-
-  public static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
-
-    Map<Origin, Set<Origin>> edges = new HashMap<>();
-
-    @Override
-    public synchronized void accept(Origin dependent, Origin dependency) {
-      edges.computeIfAbsent(dependency, s -> new HashSet<>()).add(dependent);
-    }
-  }
 }