Revert "Revised computation of forwarding methods for default interface desugaring."

This reverts commit af98ffd6ed3d28116f1ad2621286c5d48f969a6c.

Reason for revert: test failures due to expected warnings and errors

Change-Id: I1a89167dac509bfdc38189183d0bb0415d2c6442
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 b3f6a31..11c9239 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
@@ -4,43 +4,31 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.DefaultMethodCandidates;
 import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.Pair;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.LinkedList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Consumer;
-import org.objectweb.asm.Opcodes;
 
 // Default and static method interface desugaring processor for classes.
 // Adds default interface methods into the class when needed.
@@ -49,439 +37,104 @@
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
   private final InterfaceMethodRewriter rewriter;
-  private final Consumer<DexEncodedMethod> newSynthesizedMethodConsumer;
+  // Set of already processed classes.
+  private final Set<DexClass> processedClasses = Sets.newIdentityHashSet();
+  // Maps already created methods into default methods they were generated based on.
+  private final Map<DexEncodedMethod, DexEncodedMethod> createdMethods = new IdentityHashMap<>();
 
-  // Mapping from program and classpath classes to an information summary of forwarding methods.
-  private final Map<DexClass, ClassInfo> classInfo = new IdentityHashMap<>();
-  private final Map<DexLibraryClass, LibraryClassInfo> libraryClassInfo = new IdentityHashMap<>();
-
-  // Mapping from program and classpath interfaces to an information summary of default methods.
-  private final Map<DexClass, InterfaceInfo> interfaceInfo = new IdentityHashMap<>();
-
-  // Mapping from actual program classes to the synthesized forwarding methods to be created.
-  private final Map<DexProgramClass, List<DexEncodedMethod>> newSyntheticMethods =
-      new IdentityHashMap<>();
-
-  // Collection of information known at the point of a given class.
-  // This info is immutable and shared as it is often the same on a significant part of the
-  // class hierarchy. Thus, in the case of additions the parent pointer will contain prior info.
-  private static class ClassInfo {
-
-    static final ClassInfo EMPTY = new ClassInfo(null, ImmutableList.of(), ImmutableSet.of());
-
-    final ClassInfo parent;
-    final ImmutableList<DexEncodedMethod> forwardingMethods;
-    // The value DexType is null if no retargeting is required, and contains the type to retarget
-    // to if retargeting is required.
-    final ImmutableSet<DexEncodedMethod> desugaredLibraryForwardingMethods;
-
-    ClassInfo(
-        ClassInfo parent,
-        ImmutableList<DexEncodedMethod> forwardingMethods,
-        ImmutableSet<DexEncodedMethod> desugaredLibraryForwardingMethods) {
-      this.parent = parent;
-      this.forwardingMethods = forwardingMethods;
-      this.desugaredLibraryForwardingMethods = desugaredLibraryForwardingMethods;
-    }
-
-    boolean containsForwardingMethod(DexEncodedMethod method) {
-      return forwardingMethods.contains(method)
-          || (parent != null && parent.containsForwardingMethod(method));
-    }
-
-    boolean containsDesugaredLibraryForwardingMethod(DexEncodedMethod method) {
-      return desugaredLibraryForwardingMethods.contains(method)
-          || (parent != null && parent.containsDesugaredLibraryForwardingMethod(method))
-          || containsMethods(forwardingMethods, method.method);
-    }
-  }
-
-  // Collection of information known at the point of a given library class.
-  // This information is immutable and shared in an inheritance tree. In practice most library
-  // classes use the empty library class info.
-  private static class LibraryClassInfo {
-
-    static final LibraryClassInfo EMPTY = new LibraryClassInfo(ImmutableSet.of());
-
-    final ImmutableSet<DexEncodedMethod> desugaredLibraryMethods;
-
-    LibraryClassInfo(ImmutableSet<DexEncodedMethod> desugaredLibraryMethodsToImplement) {
-      this.desugaredLibraryMethods = desugaredLibraryMethodsToImplement;
-    }
-
-    static LibraryClassInfo create(ImmutableSet<DexEncodedMethod> desugaredLibraryMethods) {
-      if (desugaredLibraryMethods.isEmpty()) {
-        return EMPTY;
-      }
-      return new LibraryClassInfo(desugaredLibraryMethods);
-    }
-  }
-
-  // Collection of information known at the point of a given interface.
-  // This information is mutable and a copy exist for each interface point except for the trivial
-  // empty case which is shared.
-  private static class InterfaceInfo {
-
-    static final InterfaceInfo EMPTY =
-        new InterfaceInfo(Collections.emptySet(), Collections.emptySet());
-
-    final Set<Wrapper<DexMethod>> defaultMethods;
-    final Set<DexEncodedMethod> desugaredLibraryMethods;
-
-    InterfaceInfo(
-        Set<Wrapper<DexMethod>> defaultMethods, Set<DexEncodedMethod> desugaredLibraryMethods) {
-      this.defaultMethods = defaultMethods;
-      this.desugaredLibraryMethods = desugaredLibraryMethods;
-    }
-
-    static InterfaceInfo create(
-        Set<Wrapper<DexMethod>> defaultMethods,
-        Set<DexEncodedMethod> desugaredLibraryMethodsToImplement) {
-      return defaultMethods.isEmpty() && desugaredLibraryMethodsToImplement.isEmpty()
-          ? EMPTY
-          : new InterfaceInfo(defaultMethods, desugaredLibraryMethodsToImplement);
-    }
-  }
-
-  private static boolean containsMethods(Collection<DexEncodedMethod> methods, DexMethod method) {
-    for (DexEncodedMethod theMethod : methods) {
-      if (theMethod.method.match(method)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  ClassProcessor(
-      AppView<?> appView,
-      InterfaceMethodRewriter rewriter,
-      Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
+  ClassProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
     this.rewriter = rewriter;
-    this.newSynthesizedMethodConsumer = newSynthesizedMethodConsumer;
   }
 
-  final void addSyntheticMethods() {
-    for (DexProgramClass clazz : newSyntheticMethods.keySet()) {
-      List<DexEncodedMethod> newForwardingMethods = newSyntheticMethods.get(clazz);
-      if (newForwardingMethods != null) {
-        clazz.appendVirtualMethods(newForwardingMethods);
-      }
-    }
+  final Set<DexEncodedMethod> getForwardMethods() {
+    return createdMethods.keySet();
   }
 
-  private void addSyntheticMethod(DexProgramClass clazz, DexEncodedMethod newMethod) {
-    newSyntheticMethods.computeIfAbsent(clazz, key -> new ArrayList<>()).add(newMethod);
-    newSynthesizedMethodConsumer.accept(newMethod);
-  }
-
-  private ClassInfo computeClassInfo(DexType type) {
-    // No forwards at the top of the class hierarchy (assuming java.lang.Object is never amended).
-    if (type == null || type == dexItemFactory.objectType) {
-      return ClassInfo.EMPTY;
-    }
-    // If the type does not exist there is little we can do but assume no default methods.
-    DexClass clazz = appView.definitionFor(type);
-    if (clazz == null) {
-      return ClassInfo.EMPTY;
-    }
-    return computeClassInfo(clazz);
-  }
-
-  ClassInfo computeClassInfo(DexClass clazz) {
+  final void process(DexProgramClass clazz) {
     assert !clazz.isInterface();
-    return clazz.isLibraryClass()
-        ? ClassInfo.EMPTY
-        : classInfo.computeIfAbsent(clazz, this::computeClassInfoRaw);
-  }
-
-  private ClassInfo computeClassInfoRaw(DexClass clazz) {
-    // We compute both library and class information, but one of them is empty, since a class is
-    // a library class or is not, but cannot be both.
-    ClassInfo superInfo = computeClassInfo(clazz.superType);
-    List<DexMethod> defaultMethods = computeInterfaceInfo(clazz.interfaces.values);
-    LibraryClassInfo superLibraryClassInfo = computeLibraryClassInfo(clazz.superType);
-    assert superLibraryClassInfo.desugaredLibraryMethods.isEmpty()
-        || superInfo.desugaredLibraryForwardingMethods.isEmpty();
-    ImmutableSet<DexEncodedMethod> desugaredLibraryMethods =
-        computeDesugaredLibraryMethods(
-            clazz.interfaces.values,
-            superLibraryClassInfo.desugaredLibraryMethods.isEmpty()
-                ? superInfo.desugaredLibraryForwardingMethods
-                : superLibraryClassInfo.desugaredLibraryMethods);
-    if (defaultMethods.isEmpty() && desugaredLibraryMethods.isEmpty()) {
-      return superInfo;
+    if (!processedClasses.add(clazz)) {
+      return; // Has already been processed.
     }
-    ImmutableList<DexEncodedMethod> forwards =
-        computeDefaultMethods(clazz, superInfo, defaultMethods);
-    ImmutableSet<DexEncodedMethod> desugaredLibraryForwardingMethods =
-        computeDesugaredLibraryMethods(clazz, superInfo, desugaredLibraryMethods, forwards);
-    if (forwards.isEmpty() && desugaredLibraryForwardingMethods.isEmpty()) {
-      return superInfo;
-    }
-    return new ClassInfo(superInfo, forwards, desugaredLibraryForwardingMethods);
-  }
 
-  private ImmutableSet<DexEncodedMethod> computeDesugaredLibraryMethods(
-      DexClass clazz,
-      ClassInfo superInfo,
-      ImmutableSet<DexEncodedMethod> desugaredLibraryMethods,
-      ImmutableList<DexEncodedMethod> forwards) {
-    ImmutableSet.Builder<DexEncodedMethod> additionalDesugaredLibraryForwardingMethods =
-        ImmutableSet.builder();
-    for (DexEncodedMethod dexMethod : desugaredLibraryMethods) {
-      if (!superInfo.containsDesugaredLibraryForwardingMethod(dexMethod)
-          && !containsMethods(forwards, dexMethod.method)) {
-        additionalDesugaredLibraryForwardingMethods.add(dexMethod);
-        // We still add the methods to the list, even if override, to avoid generating it again.
-        if (clazz.lookupVirtualMethod(dexMethod.method) == null) {
-          addForwardingMethod(dexMethod, clazz);
-        }
+    // Ensure superclasses are processed first. We need it since we use information
+    // about methods added to superclasses when we decide if we want to add a default
+    // method to class `clazz`.
+    DexType superType = clazz.superType;
+    // If superClass definition is missing, just skip this part and let real processing of its
+    // subclasses report the error if it is required.
+    DexClass superClass = superType == null ? null : appView.definitionFor(superType);
+    if (superClass != null && superType != dexItemFactory.objectType) {
+      if (superClass.isInterface()) {
+        throw new CompilationError("Interface `" + superClass.toSourceString()
+            + "` used as super class of `" + clazz.toSourceString() + "`.");
+      }
+      // We assume that library classes don't need to be processed, since they
+      // are provided by a runtime not supporting default interface methods.  We
+      // also skip classpath classes, which results in sub-optimal behavior in
+      // case classpath superclass when processed adds a default method which
+      // could have been reused in this class otherwise.
+      if (superClass.isProgramClass()) {
+        process(superClass.asProgramClass());
       }
     }
-    return additionalDesugaredLibraryForwardingMethods.build();
-  }
 
-  private ImmutableList<DexEncodedMethod> computeDefaultMethods(
-      DexClass clazz, ClassInfo superInfo, List<DexMethod> defaultMethods) {
-    Builder<DexEncodedMethod> additionalForwards = ImmutableList.builder();
-    for (DexMethod defaultMethod : defaultMethods) {
-      ResolutionResult resolution = appView.appInfo().resolveMethod(clazz, defaultMethod);
-      if (resolution.isFailedResolution()) {
-        assert resolution instanceof IncompatibleClassResult;
-        addICCEThrowingMethod(defaultMethod, clazz);
-      } else {
-        DexEncodedMethod target = resolution.getSingleTarget();
-        DexClass dexClass = appView.definitionFor(target.method.holder);
-        if (target.isDefaultMethod()
-            && dexClass != null
-            && dexClass.isInterface()
-            && !superInfo.containsForwardingMethod(target)) {
-          additionalForwards.add(target);
-          addForwardingMethod(target, clazz);
-        }
-      }
-    }
-    return additionalForwards.build();
-  }
+    // When inheriting from a library class, the library class may implement interfaces to
+    // desugar. We therefore need to look at the interfaces of the library classes.
+    boolean desugaredLibraryLookup =
+        superClass != null
+            && superClass.isLibraryClass()
+            && appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().size()
+                > 0;
 
-  private LibraryClassInfo computeLibraryClassInfo(DexType type) {
-    // No desugaring required, no library class analysis.
-    if (appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
-        && appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
-      return LibraryClassInfo.EMPTY;
-    }
-    // No forwards at the top of the class hierarchy (assuming java.lang.Object is never amended).
-    if (type == null || type == dexItemFactory.objectType) {
-      return LibraryClassInfo.EMPTY;
-    }
-    // If the type does not exist there is little we can do but assume no default methods.
-    DexClass clazz = appView.definitionFor(type);
-    if (clazz == null) {
-      return LibraryClassInfo.EMPTY;
-    }
-    return computeLibraryClassInfo(clazz);
-  }
-
-  private LibraryClassInfo computeLibraryClassInfo(DexClass clazz) {
-    assert !clazz.isInterface();
-    return clazz.isLibraryClass()
-        ? libraryClassInfo.computeIfAbsent(clazz.asLibraryClass(), this::computeLibraryClassInfoRaw)
-        : LibraryClassInfo.EMPTY;
-  }
-
-  private LibraryClassInfo computeLibraryClassInfoRaw(DexLibraryClass clazz) {
-    LibraryClassInfo superInfo = computeLibraryClassInfo(clazz.superType);
-    ImmutableSet<DexEncodedMethod> desugaredLibraryMethods =
-        computeDesugaredLibraryMethods(clazz.interfaces.values, superInfo.desugaredLibraryMethods);
-    // Retarget method management.
-    for (DexEncodedMethod method : clazz.virtualMethods()) {
-      if (!method.isFinal()) {
-        Map<DexType, DexType> typeMap =
-            appView
-                .options()
-                .desugaredLibraryConfiguration
-                .getRetargetCoreLibMember()
-                .get(method.method.name);
-        if (typeMap != null) {
-          DexType dexType = typeMap.get(clazz.type);
-          if (dexType != null) {
-            ImmutableSet.Builder<DexEncodedMethod> newDesugaredLibraryMethods =
-                ImmutableSet.builder();
-            for (DexEncodedMethod desugaredLibraryForwardingMethod : desugaredLibraryMethods) {
-              if (!desugaredLibraryForwardingMethod.method.match(method.method)) {
-                newDesugaredLibraryMethods.add(desugaredLibraryForwardingMethod);
-              }
-            }
-            newDesugaredLibraryMethods.add(method);
-            desugaredLibraryMethods = newDesugaredLibraryMethods.build();
-          }
-        }
-      }
-    }
-    if (desugaredLibraryMethods == superInfo.desugaredLibraryMethods) {
-      return superInfo;
-    }
-    return LibraryClassInfo.create(desugaredLibraryMethods);
-  }
-
-  private List<DexMethod> computeInterfaceInfo(DexType[] interfaces) {
-    if (interfaces.length == 0) {
-      return Collections.emptyList();
-    }
-    Set<Wrapper<DexMethod>> defaultMethods = new HashSet<>();
-    for (DexType iface : interfaces) {
-      InterfaceInfo itfInfo = computeInterfaceInfo(iface);
-      defaultMethods.addAll(itfInfo.defaultMethods);
-    }
-    return defaultMethods.isEmpty()
-        ? Collections.emptyList()
-        : ListUtils.map(defaultMethods, Wrapper::get);
-  }
-
-  private void mergeDesugaredLibraryMethods(
-      Map<Wrapper<DexMethod>, DexEncodedMethod> desugaredLibraryMethods,
-      Set<DexEncodedMethod> methods) {
-    for (DexEncodedMethod method : methods) {
-      Wrapper<DexMethod> wrap = MethodSignatureEquivalence.get().wrap(method.method);
-      DexEncodedMethod initialMethod = desugaredLibraryMethods.get(wrap);
-      if (initialMethod == null || initialMethod.method == method.method) {
-        desugaredLibraryMethods.put(wrap, method);
-      } else {
-        // We need to do resolution, different desugared library methods are incoming from
-        // interfaces/superclass without priorities.
-        DexClass initialHolder = appView.definitionFor(initialMethod.method.holder);
-        DexClass holder = appView.definitionFor(method.method.holder);
-        assert holder != null && initialHolder != null;
-        assert holder.isInterface() || initialHolder.isInterface();
-        if (!holder.isInterface()) {
-          desugaredLibraryMethods.put(wrap, method);
-        } else if (!initialHolder.isInterface()) {
-          desugaredLibraryMethods.put(wrap, initialMethod);
-        } else if (implementsInterface(initialHolder, holder)) {
-          desugaredLibraryMethods.put(wrap, initialMethod);
-        } else {
-          desugaredLibraryMethods.put(wrap, method);
-        }
-      }
-    }
-  }
-
-  private boolean implementsInterface(DexClass initialHolder, DexClass holder) {
-    LinkedList<DexType> workList = new LinkedList<>();
-    Collections.addAll(workList, initialHolder.interfaces.values);
-    while (!workList.isEmpty()) {
-      DexType dexType = workList.removeFirst();
-      if (dexType == holder.type) {
-        return true;
-      }
-      DexClass dexClass = appView.definitionFor(dexType);
-      assert dexClass != null;
-      Collections.addAll(workList, dexClass.interfaces.values);
-    }
-    return false;
-  }
-
-  private ImmutableSet<DexEncodedMethod> computeDesugaredLibraryMethods(
-      DexType[] interfaces, ImmutableSet<DexEncodedMethod> incomingDesugaredLibraryMethods) {
-    Map<Wrapper<DexMethod>, DexEncodedMethod> desugaredLibraryMethods = new HashMap<>();
-    for (DexType iface : interfaces) {
-      mergeDesugaredLibraryMethods(
-          desugaredLibraryMethods, computeInterfaceInfo(iface).desugaredLibraryMethods);
-    }
-    if (desugaredLibraryMethods.isEmpty()) {
-      return incomingDesugaredLibraryMethods;
-    }
-    mergeDesugaredLibraryMethods(desugaredLibraryMethods, incomingDesugaredLibraryMethods);
-    return desugaredLibraryMethods.isEmpty()
-        ? ImmutableSet.of()
-        : ImmutableSet.copyOf(desugaredLibraryMethods.values());
-  }
-
-  private InterfaceInfo computeInterfaceInfo(DexType iface) {
-    if (iface == null || iface == dexItemFactory.objectType) {
-      return InterfaceInfo.EMPTY;
-    }
-    DexClass definition = appView.definitionFor(iface);
-    if (definition == null) {
-      return InterfaceInfo.EMPTY;
-    }
-    return computeInterfaceInfo(definition);
-  }
-
-  private InterfaceInfo computeInterfaceInfo(DexClass iface) {
-    return interfaceInfo.computeIfAbsent(iface, this::computeInterfaceInfoRaw);
-  }
-
-  private InterfaceInfo computeInterfaceInfoRaw(DexClass iface) {
-    assert iface.isInterface();
-    assert iface.superType == dexItemFactory.objectType;
-    Set<Wrapper<DexMethod>> defaultMethods = new HashSet<>();
-    Set<DexEncodedMethod> desugaredLibraryMethods = new HashSet<>();
-    for (DexType superiface : iface.interfaces.values) {
-      defaultMethods.addAll(computeInterfaceInfo(superiface).defaultMethods);
-      desugaredLibraryMethods.addAll(computeInterfaceInfo(superiface).desugaredLibraryMethods);
-    }
-    // NOTE: we intentionally don't desugar default methods into interface methods
-    // coming from android.jar since it is only possible in case v24+ version
-    // of android.jar is provided. The only exception is desugared library classes.
-    if (!iface.isLibraryClass() || appView.rewritePrefix.hasRewrittenType(iface.type)) {
-      for (DexEncodedMethod method : iface.methods()) {
-        if (method.isDefaultMethod()) {
-          defaultMethods.add(MethodSignatureEquivalence.get().wrap(method.method));
-        }
-      }
-    }
-    if (appView
-        .options()
-        .desugaredLibraryConfiguration
-        .getEmulateLibraryInterface()
-        .containsKey(iface.type)) {
-      for (DexEncodedMethod method : iface.methods()) {
-        if (method.isDefaultMethod() && shouldEmulate(method.method)) {
-          desugaredLibraryMethods.add(method);
-        }
-      }
-    }
-    return InterfaceInfo.create(defaultMethods, desugaredLibraryMethods);
-  }
-
-  private boolean shouldEmulate(DexMethod method) {
-    List<Pair<DexType, DexString>> dontRewriteInvocation =
-        appView.options().desugaredLibraryConfiguration.getDontRewriteInvocation();
-    for (Pair<DexType, DexString> pair : dontRewriteInvocation) {
-      if (pair.getFirst() == method.holder && pair.getSecond() == method.name) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  private void addICCEThrowingMethod(DexMethod method, DexClass clazz) {
-    if (!clazz.isProgramClass()) {
+    if (clazz.interfaces.isEmpty() && !desugaredLibraryLookup) {
+      // Since superclass has already been processed and it has all missing methods
+      // added, these methods will be inherited by `clazz`, and only need to be revised
+      // in case this class has *additional* interfaces implemented, which may change
+      // the entire picture of the default method selection in runtime.
       return;
     }
-    DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
-    DexEncodedMethod newEncodedMethod =
-        new DexEncodedMethod(
-            newMethod,
-            MethodAccessFlags.fromCfAccessFlags(Opcodes.ACC_PUBLIC, false),
-            DexAnnotationSet.empty(),
-            ParameterAnnotationsList.empty(),
-            new SynthesizedCode(
-                callerPosition ->
-                    new ExceptionThrowingSourceCode(
-                        clazz.type, method, callerPosition, dexItemFactory.icceType)));
-    addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
-  }
 
-  // Note: The parameter defaultMethod may be a public method on a class in case of desugared
-  // library retargeting (See below target.isInterface check).
-  private void addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
-    if (!clazz.isProgramClass()) {
+    // Collect the default interface methods to be added to this class.
+    DefaultMethodCandidates methodsToImplement =
+        collectMethodsToImplement(clazz, desugaredLibraryLookup);
+    if (methodsToImplement.isEmpty()) {
       return;
     }
+
+    // Add the methods.
+    List<DexEncodedMethod> newForwardingMethods = new ArrayList<>(methodsToImplement.size());
+    for (DexEncodedMethod method : methodsToImplement.candidates) {
+      assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
+      DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
+      newForwardingMethods.add(newMethod);
+      createdMethods.put(newMethod, method);
+    }
+    for (DexEncodedMethod conflict : methodsToImplement.conflicts.keySet()) {
+      assert conflict.accessFlags.isPublic() && !conflict.accessFlags.isAbstract();
+      DexEncodedMethod newMethod = addICCEThrowingMethod(conflict, clazz);
+      newForwardingMethods.add(newMethod);
+      createdMethods.put(newMethod, conflict);
+    }
+    clazz.appendVirtualMethods(newForwardingMethods);
+  }
+
+  private DexEncodedMethod addICCEThrowingMethod(DexEncodedMethod method, DexClass clazz) {
+    DexMethod newMethod =
+        dexItemFactory.createMethod(clazz.type, method.method.proto, method.method.name);
+    return new DexEncodedMethod(
+        newMethod,
+        method.accessFlags.copy(),
+        DexAnnotationSet.empty(),
+        ParameterAnnotationsList.empty(),
+        new SynthesizedCode(
+            callerPosition ->
+                new ExceptionThrowingSourceCode(
+                    clazz.type, method.method, callerPosition, dexItemFactory.icceType)));
+  }
+
+  private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
     DexMethod method = defaultMethod.method;
     DexClass target = appView.definitionFor(method.holder);
     // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
@@ -505,14 +158,12 @@
         .setTarget(forwardMethod)
         .setInvokeType(Invoke.Type.STATIC)
         .setIsInterface(false); // Holder is companion class, not an interface.
-    DexEncodedMethod newEncodedMethod =
-        new DexEncodedMethod(
-            newMethod,
-            newFlags,
-            defaultMethod.annotations,
-            defaultMethod.parameterAnnotationsList,
-            new SynthesizedCode(forwardSourceCodeBuilder::build));
-    addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
+    return new DexEncodedMethod(
+        newMethod,
+        newFlags,
+        defaultMethod.annotations,
+        defaultMethod.parameterAnnotationsList,
+        new SynthesizedCode(forwardSourceCodeBuilder::build));
   }
 
   private DexMethod retargetMethod(DexMethod method) {
@@ -526,4 +177,166 @@
         dexItemFactory.prependTypeToProto(method.holder, method.proto),
         method.name);
   }
+
+  // For a given class `clazz` inspects all interfaces it implements directly or
+  // indirectly and collect a set of all default methods to be implemented
+  // in this class.
+  private DefaultMethodCandidates collectMethodsToImplement(
+      DexClass clazz, boolean desugaredLibraryLookup) {
+    DefaultMethodsHelper helper = new DefaultMethodsHelper();
+    DexClass current = clazz;
+    List<DexEncodedMethod> accumulatedVirtualMethods = new ArrayList<>();
+    // Collect candidate default methods by inspecting interfaces implemented
+    // by this class as well as its superclasses.
+    //
+    // We assume here that interfaces implemented by java.lang.Object don't
+    // have default methods to desugar since they are library interfaces. And we assume object
+    // methods don't hide any default interface methods. Default interface method matching Object's
+    // methods is supposed to fail with a compilation error.
+    // Note that this last assumption will be broken if Object API is augmented with a new method in
+    // the future.
+    while (current.type != dexItemFactory.objectType) {
+      for (DexType type : current.interfaces.values) {
+        helper.merge(rewriter.getOrCreateInterfaceInfo(clazz, current, type));
+      }
+
+      // TODO(anyone): Using clazz here instead of current looks suspicious, should this be hoisted
+      // out of the loop or changed to current?
+      accumulatedVirtualMethods.addAll(clazz.virtualMethods());
+
+      List<DexEncodedMethod> defaultMethodsInDirectInterface = helper.createFullList();
+
+      List<DexEncodedMethod> toBeImplementedFromDirectInterface =
+          new ArrayList<>(defaultMethodsInDirectInterface.size());
+      hideCandidates(accumulatedVirtualMethods,
+          defaultMethodsInDirectInterface,
+          toBeImplementedFromDirectInterface);
+      // toBeImplementedFromDirectInterface are those that we know for sure we need to implement by
+      // looking at the already desugared super classes.
+      // Remaining methods in defaultMethodsInDirectInterface are those methods we need to look at
+      // the hierarchy to know how they should be handled.
+      if (toBeImplementedFromDirectInterface.isEmpty()
+          && defaultMethodsInDirectInterface.isEmpty()
+          && !desugaredLibraryLookup) {
+        // No interface with default in direct hierarchy, nothing to do: super already has all that
+        // is needed.
+        return DefaultMethodCandidates.empty();
+      }
+
+      if (current.superType == null) {
+        // TODO(anyone): Can this ever happen? It seems the loop stops on Object.
+        break;
+      } else {
+        DexClass superClass = appView.definitionFor(current.superType);
+        if (superClass != null) {
+          // TODO(b/138988172): Can we avoid traversing the full hierarchy for each type?
+          InterfaceMethodRewriter.reportDependencyEdge(superClass, current, appView);
+          current = superClass;
+        } else {
+          String message = "Default method desugaring of `" + clazz.toSourceString() + "` failed";
+          if (current == clazz) {
+            message += " because its super class `" +
+                clazz.superType.toSourceString() + "` is missing";
+          } else {
+            message +=
+                " because it's hierarchy is incomplete. The class `"
+                    + current.superType.toSourceString()
+                    + "` is missing and it is the declared super class of `"
+                    + current.toSourceString() + "`";
+          }
+          throw new CompilationError(message);
+        }
+      }
+    }
+
+    DefaultMethodCandidates candidateSet = helper.createCandidatesList();
+    if (candidateSet.isEmpty()) {
+      return candidateSet;
+    }
+
+    // Remove from candidates methods defined in class or any of its superclasses.
+    List<DexEncodedMethod> candidates = candidateSet.candidates;
+    List<DexEncodedMethod> toBeImplemented = new ArrayList<>(candidates.size());
+    current = clazz;
+    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+    while (true) {
+      // In desugared library look-up, methods from library classes cannot hide methods from
+      // emulated interfaces (the method being desugared implied the implementation is not
+      // present in the library class), except through retarget core lib member.
+      if (desugaredLibraryLookup && current.isLibraryClass()) {
+        Iterator<DexEncodedMethod> iterator = candidates.iterator();
+        while (iterator.hasNext()) {
+          DexEncodedMethod candidate = iterator.next();
+          if (rewriter.isEmulatedInterface(candidate.method.holder)
+              && current.lookupVirtualMethod(candidate.method) != null) {
+            // A library class overrides an emulated interface method. This override is valid
+            // only if it goes through retarget core lib member, else it needs to be implemented.
+            Map<DexType, DexType> typeMap = retargetCoreLibMember.get(candidate.method.name);
+            if (typeMap != null && typeMap.containsKey(current.type)) {
+              // A rewrite needs to be performed, but instead of rewriting to the companion class,
+              // D8/R8 needs to rewrite to the retarget member.
+              toBeImplemented.add(current.lookupVirtualMethod(candidate.method));
+            } else {
+              toBeImplemented.add(candidate);
+              iterator.remove();
+            }
+          }
+        }
+      }
+      // Hide candidates by virtual method of the class.
+      hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
+      if (candidates.isEmpty()) {
+        return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
+      }
+
+      DexType superType = current.superType;
+      DexClass superClass = null;
+      if (superType != null) {
+        superClass = appView.definitionFor(superType);
+        // It's available or we would have failed while analyzing the hierarchy for interfaces.
+        assert superClass != null;
+      }
+      if (superClass == null || superType == dexItemFactory.objectType) {
+        // Note that default interface methods must never have same
+        // name/signature as any method in java.lang.Object (JLS §9.4.1.2).
+
+        // Everything still in candidate list is not hidden.
+        toBeImplemented.addAll(candidates);
+
+        return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
+      }
+      current = superClass;
+    }
+  }
+
+  private void hideCandidates(
+      List<DexEncodedMethod> virtualMethods,
+      Collection<DexEncodedMethod> candidates,
+      List<DexEncodedMethod> toBeImplemented) {
+    Iterator<DexEncodedMethod> it = candidates.iterator();
+    while (it.hasNext()) {
+      DexEncodedMethod candidate = it.next();
+      for (DexEncodedMethod encoded : virtualMethods) {
+        if (candidate.method.match(encoded)) {
+          // Found a methods hiding the candidate.
+          DexEncodedMethod basedOnCandidate = createdMethods.get(encoded);
+          if (basedOnCandidate != null) {
+            // The method we found is a method we have generated for a default interface
+            // method in a superclass. If the method is based on the same candidate we don't
+            // need to re-generate this method again since it is going to be inherited.
+            if (basedOnCandidate != candidate) {
+              // Need to re-generate since the inherited version is
+              // based on a different candidate.
+              toBeImplemented.add(candidate);
+            }
+          }
+
+          // Done with this candidate.
+          it.remove();
+          break;
+        }
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
index 07277bc..2484113 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
@@ -6,15 +6,66 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Set;
 
 // Helper class implementing bunch of default interface method handling operations.
 final class DefaultMethodsHelper {
+
+  // Collection of default methods that need to have generated forwarding methods.
+  public static class DefaultMethodCandidates {
+    final List<DexEncodedMethod> candidates;
+    final Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts;
+
+    private static final DefaultMethodCandidates EMPTY =
+        new DefaultMethodCandidates(Collections.emptyList(), Collections.emptyMap());
+
+    public static DefaultMethodCandidates empty() {
+      return EMPTY;
+    }
+
+    public DefaultMethodCandidates(
+        List<DexEncodedMethod> candidates,
+        Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts) {
+      this.candidates = candidates;
+      this.conflicts = conflicts;
+    }
+
+    public int size() {
+      return candidates.size() + conflicts.size();
+    }
+
+    public boolean isEmpty() {
+      return candidates.isEmpty() && conflicts.isEmpty();
+    }
+  }
+
+  // Equivalence wrapper for comparing two method signatures modulo holder type.
+  private static class SignatureEquivalence extends Equivalence<DexEncodedMethod> {
+
+    @Override
+    protected boolean doEquivalent(DexEncodedMethod method1, DexEncodedMethod method2) {
+      return method1.method.match(method2.method);
+    }
+
+    @Override
+    protected int doHash(DexEncodedMethod method) {
+      return Objects.hash(method.method.name, method.method.proto);
+    }
+  }
+
   // Current set of default interface methods, may overlap with `hidden`.
   private final Set<DexEncodedMethod> candidates = Sets.newIdentityHashSet();
   // Current set of known hidden default interface methods.
@@ -76,6 +127,63 @@
     candidates.add(encoded);
   }
 
+  final DefaultMethodCandidates createCandidatesList() {
+    // The common cases is for no default methods or a single one.
+    if (candidates.isEmpty()) {
+      return DefaultMethodCandidates.empty();
+    }
+    if (candidates.size() == 1 && hidden.isEmpty()) {
+      return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
+    }
+    // In case there are more we need to check for potential duplicates and treat them specially
+    // to preserve the IncompatibleClassChangeError that would arise at runtime.
+    int maxSize = candidates.size();
+    SignatureEquivalence equivalence = new SignatureEquivalence();
+    Map<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> groups = new HashMap<>(maxSize);
+    boolean foundConflicts = false;
+    for (DexEncodedMethod candidate : candidates) {
+      if (hidden.contains(candidate)) {
+        continue;
+      }
+      Wrapper<DexEncodedMethod> key = equivalence.wrap(candidate);
+      List<DexEncodedMethod> conflicts = groups.get(key);
+      if (conflicts != null) {
+        foundConflicts = true;
+      } else {
+        conflicts = new ArrayList<>(maxSize);
+        groups.put(key, conflicts);
+      }
+      conflicts.add(candidate);
+    }
+    // In the fast path we don't expect any conflicts or hidden candidates.
+    if (!foundConflicts && hidden.isEmpty()) {
+      return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
+    }
+    // Slow case in the case of conflicts or hidden candidates build the result.
+    List<DexEncodedMethod> actualCandidates = new ArrayList<>(groups.size());
+    Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts = new IdentityHashMap<>();
+    for (Entry<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> entry : groups.entrySet()) {
+      if (entry.getValue().size() == 1) {
+        actualCandidates.add(entry.getKey().get());
+      } else {
+        conflicts.put(entry.getKey().get(), entry.getValue());
+      }
+    }
+    return new DefaultMethodCandidates(actualCandidates, conflicts);
+  }
+
+  final List<DexEncodedMethod> createFullList() {
+    if (candidates.isEmpty() && hidden.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    List<DexEncodedMethod> fullList =
+        new ArrayList<DexEncodedMethod>(candidates.size() + hidden.size());
+    fullList.addAll(candidates);
+    fullList.addAll(hidden);
+    return fullList;
+  }
+
   // Create default interface collection based on collected information.
   final Collection wrapInCollection() {
     candidates.removeAll(hidden);
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 9cacdd9..eb855d1 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
@@ -60,7 +60,6 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
 
 //
 // Default and static interface method desugaring rewriter (note that lambda
@@ -1042,7 +1041,7 @@
 
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
-    processClasses(builder, flavour, synthesizedMethods::add);
+    synthesizedMethods.addAll(processClasses(builder, flavour));
 
     // Process interfaces, create companion or dispatch class if needed, move static
     // methods to companion class, copy default interface methods to companion classes,
@@ -1096,17 +1095,14 @@
     return processor.syntheticClasses;
   }
 
-  private void processClasses(
-      Builder<?> builder, Flavor flavour, Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
-    ClassProcessor processor = new ClassProcessor(appView, this, newSynthesizedMethodConsumer);
-    // First we compute all desugaring *without* introducing forwarding methods.
+  private Set<DexEncodedMethod> processClasses(Builder<?> builder, Flavor flavour) {
+    ClassProcessor processor = new ClassProcessor(appView, this);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, false)) {
-        processor.computeClassInfo(clazz);
+        processor.process(clazz);
       }
     }
-    // Then we introduce forwarding methods.
-    processor.addSyntheticMethods();
+    return processor.getForwardMethods();
   }
 
   final boolean isDefaultMethod(DexEncodedMethod method) {
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 1c20983..8b8ef05 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
@@ -62,6 +62,7 @@
 
   void process(DexProgramClass iface, NestedGraphLense.Builder graphLensBuilder) {
     assert iface.isInterface();
+
     // The list of methods to be created in companion class.
     List<DexEncodedMethod> companionMethods = new ArrayList<>();
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java
index 1c33f17..e176fca 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java
@@ -103,10 +103,7 @@
     String[] lines = stdOut.split("\n");
     assert lines.length % 2 == 0;
     for (int i = 0; i < lines.length; i += 2) {
-      assertEquals(
-          "Different lines: " + lines[i] + " || " + lines[i + 1] + "\n" + stdOut,
-          lines[i],
-          lines[i + 1]);
+      assertEquals(lines[i], lines[i + 1]);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
deleted file mode 100644
index 602ee7c..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
+++ /dev/null
@@ -1,255 +0,0 @@
-// 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.desugaredlibrary;
-
-import static junit.framework.TestCase.assertTrue;
-
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.APIConversionTestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Spliterator;
-import org.jetbrains.annotations.NotNull;
-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 CustomCollectionForwardingTest extends APIConversionTestBase {
-
-  private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
-
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
-  }
-
-  public CustomCollectionForwardingTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void testCustomCollectionD8() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForD8()
-        .addInnerClasses(CustomCollectionForwardingTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .inspect(this::assertForwardingMethods)
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibraryWithConversionExtension,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
-        .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutput(
-            StringUtils.lines("false", "false", "false", "false", "false", "false"));
-  }
-
-  private void assertForwardingMethods(CodeInspector inspector) {
-    if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
-      return;
-    }
-    ClassSubject cal = inspector.clazz(CustomArrayList.class);
-    MethodSubject spliteratorCal = cal.method("j$.util.Spliterator", "spliterator");
-    assertTrue(spliteratorCal.isPresent());
-    assertTrue(
-        spliteratorCal
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
-    MethodSubject streamCal = cal.method("j$.util.stream.Stream", "stream");
-    assertTrue(streamCal.isPresent());
-    assertTrue(
-        streamCal
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
-
-    ClassSubject clhs = inspector.clazz(CustomLinkedHashSet.class);
-    MethodSubject spliteratorClhs = clhs.method("j$.util.Spliterator", "spliterator");
-    assertTrue(spliteratorClhs.isPresent());
-    assertTrue(
-        spliteratorClhs
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("DesugarLinkedHashSet")));
-    MethodSubject streamClhs = clhs.method("j$.util.stream.Stream", "stream");
-    assertTrue(streamClhs.isPresent());
-    assertTrue(
-        streamClhs
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
-
-    ClassSubject cl = inspector.clazz(CustomList.class);
-    MethodSubject spliteratorCl = cl.method("j$.util.Spliterator", "spliterator");
-    assertTrue(spliteratorCl.isPresent());
-    assertTrue(
-        spliteratorCl
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
-    MethodSubject streamCl = cl.method("j$.util.stream.Stream", "stream");
-    assertTrue(streamCl.isPresent());
-    assertTrue(
-        streamCl
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
-  }
-
-  static class Executor {
-
-    public static void main(String[] args) {
-      CustomArrayList<Object> objects = new CustomArrayList<>();
-      System.out.println(objects.spliterator().hasCharacteristics(Spliterator.CONCURRENT));
-      System.out.println(objects.stream().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
-
-      CustomLinkedHashSet<Object> objects2 = new CustomLinkedHashSet<>();
-      System.out.println(objects2.spliterator().hasCharacteristics(Spliterator.CONCURRENT));
-      System.out.println(
-          objects2.stream().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
-
-      CustomList<Object> objects3 = new CustomList<>();
-      System.out.println(objects3.spliterator().hasCharacteristics(Spliterator.CONCURRENT));
-      System.out.println(
-          objects3.stream().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
-    }
-  }
-
-  static class CustomArrayList<E> extends ArrayList<E> implements Collection<E> {}
-
-  static class CustomLinkedHashSet<E> extends LinkedHashSet<E> implements Collection<E> {}
-
-  static class CustomList<E> implements Collection<E>, List<E> {
-
-    @Override
-    public int size() {
-      return 0;
-    }
-
-    @Override
-    public boolean isEmpty() {
-      return false;
-    }
-
-    @Override
-    public boolean contains(Object o) {
-      return false;
-    }
-
-    @NotNull
-    @Override
-    public Iterator<E> iterator() {
-      return null;
-    }
-
-    @NotNull
-    @Override
-    public Object[] toArray() {
-      return new Object[0];
-    }
-
-    @NotNull
-    @Override
-    public <T> T[] toArray(@NotNull T[] a) {
-      return null;
-    }
-
-    @Override
-    public boolean add(E e) {
-      return false;
-    }
-
-    @Override
-    public boolean remove(Object o) {
-      return false;
-    }
-
-    @Override
-    public boolean containsAll(@NotNull Collection<?> c) {
-      return false;
-    }
-
-    @Override
-    public boolean addAll(@NotNull Collection<? extends E> c) {
-      return false;
-    }
-
-    @Override
-    public boolean addAll(int index, @NotNull Collection<? extends E> c) {
-      return false;
-    }
-
-    @Override
-    public boolean removeAll(@NotNull Collection<?> c) {
-      return false;
-    }
-
-    @Override
-    public boolean retainAll(@NotNull Collection<?> c) {
-      return false;
-    }
-
-    @Override
-    public void clear() {}
-
-    @Override
-    public E get(int index) {
-      return null;
-    }
-
-    @Override
-    public E set(int index, E element) {
-      return null;
-    }
-
-    @Override
-    public void add(int index, E element) {}
-
-    @Override
-    public E remove(int index) {
-      return null;
-    }
-
-    @Override
-    public int indexOf(Object o) {
-      return 0;
-    }
-
-    @Override
-    public int lastIndexOf(Object o) {
-      return 0;
-    }
-
-    @NotNull
-    @Override
-    public ListIterator<E> listIterator() {
-      return null;
-    }
-
-    @NotNull
-    @Override
-    public ListIterator<E> listIterator(int index) {
-      return null;
-    }
-
-    @NotNull
-    @Override
-    public List<E> subList(int fromIndex, int toIndex) {
-      return null;
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
index d81cdf4..874d4ad 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
@@ -233,7 +233,6 @@
           compileResult.run(
               parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
       assertTrue(
-          "Failure in " + path + "\n" + result,
           result
               .getStdOut()
               .endsWith(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11TimeTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11TimeTests.java
index aad3e3a..58acdc7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11TimeTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11TimeTests.java
@@ -159,9 +159,7 @@
       } else if (result.getStdErr().contains("no microsecond precision")) {
         // Emulator precision, won't fix.
       } else {
-        assertTrue(
-            "Failure in " + success + "\n" + result,
-            result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
+        assertTrue(result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
       }
     }
     for (String success : forEachProblem) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index 0eaf18b..819e6ce 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
@@ -100,18 +99,17 @@
   @Test
   public void testHelloCompiledWithD8Dex() throws Exception {
     Path helloOutput = temp.newFolder("helloOutput").toPath().resolve("out.zip").toAbsolutePath();
-    D8TestRunResult run =
-        compileR8ToDexWithD8()
-            .run(
-                parameters.getRuntime(),
-                D8.class,
-                "--release",
-                "--output",
-                helloOutput.toString(),
-                "--lib",
-                commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
-                HELLO_PATH);
-    run.assertSuccess();
+    compileR8ToDexWithD8()
+        .run(
+            parameters.getRuntime(),
+            D8.class,
+            "--release",
+            "--output",
+            helloOutput.toString(),
+            "--lib",
+            commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
+            HELLO_PATH)
+        .assertSuccess();
     verifyResult(helloOutput);
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java
deleted file mode 100644
index 3317df1..0000000
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// 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.desugaring.interfacemethods;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-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 ReturnTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
-  }
-
-  public ReturnTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void testReturn() throws Exception {
-    testForRuntime(parameters)
-        .addInnerClasses(ReturnTest.class)
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(
-            StringUtils.lines(
-                "com.android.tools.r8.desugaring.interfacemethods.ReturnTest$SuperA",
-                "com.android.tools.r8.desugaring.interfacemethods.ReturnTest$A"));
-  }
-
-  static class Main {
-
-    public static void main(String[] args) {
-      new SuperA().print();
-      new A().print();
-    }
-  }
-
-  static class SuperA implements SuperI {
-    public void print() {
-      SuperA a = get();
-      System.out.println(a.getClass().getName());
-    }
-  }
-
-  static class A extends SuperA implements I {
-    public void print() {
-      A a = get();
-      System.out.println(a.getClass().getName());
-    }
-  }
-
-  interface I extends SuperI {
-    default A get() {
-      return new A();
-    }
-  }
-
-  interface SuperI {
-    default SuperA get() {
-      return new SuperA();
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/TrapeziumTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/TrapeziumTest.java
deleted file mode 100644
index ee0308f..0000000
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/TrapeziumTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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.desugaring.interfacemethods;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-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 TrapeziumTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
-  }
-
-  public TrapeziumTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void testTrapezium() throws Exception {
-    testForRuntime(parameters)
-        .addInnerClasses(TrapeziumTest.class)
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(StringUtils.lines("foo from superI", "foo from I"));
-  }
-
-  static class Main {
-
-    public static void main(String[] args) {
-      new SuperA().foo();
-      new A().foo();
-    }
-  }
-
-  static class SuperA implements SuperI {}
-
-  static class A extends SuperA implements I {}
-
-  interface I extends SuperI {
-    default void foo() {
-      System.out.println("foo from I");
-    }
-  }
-
-  interface SuperI {
-    default void foo() {
-      System.out.println("foo from superI");
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 535bceb..2bc1dd4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -70,10 +70,6 @@
   public static MethodSignature MAIN =
       new MethodSignature("main", "void", new String[] {"java.lang.String[]"});
 
-  public CodeInspector(String path) throws IOException, ExecutionException {
-    this(Paths.get(path));
-  }
-
   public CodeInspector(Path file, String mappingFile) throws IOException, ExecutionException {
     this(Collections.singletonList(file), mappingFile, null);
   }