Trace failure dependencies in markProgramMethodOverridesAsLive

Change-Id: I931ecaa708e852af8561673c1cb2fa5e500ebb24
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index dc8e0bb..12a5e8d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -794,15 +794,23 @@
     return builder.resolve(clazz);
   }
 
-  // Non-private lookup (ie, not resolution) to find interface targets.
-  DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
-    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
-    resolveMethodStep3Helper(method.getProto(), method.getName(), clazz, builder);
-    return builder.lookup();
+  MethodResolutionResult resolveMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+    return resolveMaximallySpecificTargetHelper(clazz, method).resolve(clazz);
   }
 
-  // Non-private lookup (ie, not resolution) to find interface targets.
-  DexClassAndMethod lookupMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
+  private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
+      DexClass clazz, DexMethod method) {
+    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+    resolveMethodStep3Helper(method.getProto(), method.getName(), clazz, builder);
+    return builder;
+  }
+
+  MethodResolutionResult resolveMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
+    return resolveMaximallySpecificTargetHelper(lambda, method).internalResolve(null);
+  }
+
+  private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
+      LambdaDescriptor lambda, DexMethod method) {
     MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
     resolveMethodStep3Helper(
         method.getProto(),
@@ -810,7 +818,17 @@
         dexItemFactory().objectType,
         lambda.interfaces,
         builder);
-    return builder.lookup();
+    return builder;
+  }
+
+  // Non-private lookup (ie, not resolution) to find interface targets.
+  DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+    return resolveMaximallySpecificTargetHelper(clazz, method).lookup();
+  }
+
+  // Non-private lookup (ie, not resolution) to find interface targets.
+  DexClassAndMethod lookupMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
+    return resolveMaximallySpecificTargetHelper(lambda, method).lookup();
   }
 
   /** Helper method that builds the set of maximally specific methods. */
@@ -1059,10 +1077,7 @@
     }
 
     DexClassAndMethod lookup() {
-      SingleResolutionResult result = internalResolve(null).asSingleResolution();
-      return result != null
-          ? DexClassAndMethod.create(result.getResolvedHolder(), result.getResolvedMethod())
-          : null;
+      return internalResolve(null).getResolutionPair();
     }
 
     MethodResolutionResult resolve(DexClass initialResolutionHolder) {
diff --git a/src/main/java/com/android/tools/r8/graph/LookupResult.java b/src/main/java/com/android/tools/r8/graph/LookupResult.java
index 17d7f55..bc85814 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupResult.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupResult.java
@@ -5,9 +5,10 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.function.Consumer;
 
 public abstract class LookupResult {
@@ -28,18 +29,23 @@
     return null;
   }
 
-  public final void forEach(Consumer<LookupTarget> onTarget) {
-    forEach(onTarget::accept, onTarget::accept);
+  public final void forEach(Consumer<? super LookupTarget> onTarget) {
+    forEach(onTarget, onTarget);
   }
 
   public abstract void forEach(
-      Consumer<DexClassAndMethod> onMethodTarget, Consumer<LookupLambdaTarget> onLambdaTarget);
+      Consumer<? super DexClassAndMethod> onMethodTarget,
+      Consumer<? super LookupLambdaTarget> onLambdaTarget);
+
+  public abstract void forEachFailureDependency(
+      Consumer<? super DexEncodedMethod> methodCausingFailureConsumer);
 
   public static LookupResultSuccess createResult(
-      Map<DexEncodedMethod, DexClassAndMethod> methodTargets,
+      DexClassAndMethodSet methodTargets,
       List<LookupLambdaTarget> lambdaTargets,
+      List<DexEncodedMethod> methodsCausingFailure,
       LookupResultCollectionState state) {
-    return new LookupResultSuccess(methodTargets, lambdaTargets, state);
+    return new LookupResultSuccess(methodTargets, lambdaTargets, methodsCausingFailure, state);
   }
 
   public static LookupResultFailure createFailedResult() {
@@ -54,23 +60,31 @@
 
     private static final LookupResultSuccess EMPTY_INSTANCE =
         new LookupResultSuccess(
-            Collections.emptyMap(),
+            DexClassAndMethodSet.empty(),
+            Collections.emptyList(),
             Collections.emptyList(),
             LookupResultCollectionState.Incomplete);
 
-    private final Map<DexEncodedMethod, DexClassAndMethod> methodTargets;
+    private final DexClassAndMethodSet methodTargets;
     private final List<LookupLambdaTarget> lambdaTargets;
+    private final List<DexEncodedMethod> methodsCausingFailure;
     private LookupResultCollectionState state;
 
     private LookupResultSuccess(
-        Map<DexEncodedMethod, DexClassAndMethod> methodTargets,
+        DexClassAndMethodSet methodTargets,
         List<LookupLambdaTarget> lambdaTargets,
+        List<DexEncodedMethod> methodsCausingFailure,
         LookupResultCollectionState state) {
       this.methodTargets = methodTargets;
       this.lambdaTargets = lambdaTargets;
+      this.methodsCausingFailure = methodsCausingFailure;
       this.state = state;
     }
 
+    public static Builder builder() {
+      return new Builder();
+    }
+
     public boolean isEmpty() {
       return methodTargets.isEmpty() && lambdaTargets.isEmpty();
     }
@@ -85,14 +99,21 @@
 
     @Override
     public void forEach(
-        Consumer<DexClassAndMethod> onMethodTarget, Consumer<LookupLambdaTarget> onLambdaTarget) {
-      methodTargets.forEach((ignore, method) -> onMethodTarget.accept(method));
+        Consumer<? super DexClassAndMethod> onMethodTarget,
+        Consumer<? super LookupLambdaTarget> onLambdaTarget) {
+      methodTargets.forEach(onMethodTarget);
       lambdaTargets.forEach(onLambdaTarget);
     }
 
+    @Override
+    public void forEachFailureDependency(
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+      methodsCausingFailure.forEach(methodCausingFailureConsumer);
+    }
+
     public boolean contains(DexEncodedMethod method) {
       // Containment of a method in the lookup results only pertains to the method targets.
-      return methodTargets.containsKey(method);
+      return methodTargets.contains(method);
     }
 
     @Override
@@ -124,7 +145,7 @@
       }
       // TODO(b/150932978): Check lambda targets implementation methods.
       if (methodTargets.size() == 1) {
-        return methodTargets.values().iterator().next();
+        return methodTargets.iterator().next();
       } else if (lambdaTargets.size() == 1) {
         return lambdaTargets.get(0);
       }
@@ -135,6 +156,38 @@
       Complete,
       Incomplete,
     }
+
+    public static class Builder {
+
+      private final DexClassAndMethodSet methodTargets = DexClassAndMethodSet.create();
+      private final List<LookupLambdaTarget> lambdaTargets = new ArrayList<>();
+      private final List<DexEncodedMethod> methodsCausingFailure = new ArrayList<>();
+      private LookupResultCollectionState state;
+
+      public Builder addMethodTarget(DexClassAndMethod methodTarget) {
+        methodTargets.add(methodTarget);
+        return this;
+      }
+
+      public Builder addLambdaTarget(LookupLambdaTarget lambdaTarget) {
+        lambdaTargets.add(lambdaTarget);
+        return this;
+      }
+
+      public Builder addMethodCausingFailure(DexEncodedMethod methodCausingFailure) {
+        methodsCausingFailure.add(methodCausingFailure);
+        return this;
+      }
+
+      public Builder setState(LookupResultCollectionState state) {
+        this.state = state;
+        return this;
+      }
+
+      public LookupResultSuccess build() {
+        return new LookupResultSuccess(methodTargets, lambdaTargets, methodsCausingFailure, state);
+      }
+    }
   }
 
   public static class LookupResultFailure extends LookupResult {
@@ -157,8 +210,15 @@
 
     @Override
     public void forEach(
-        Consumer<DexClassAndMethod> onMethodTarget, Consumer<LookupLambdaTarget> onLambdaTarget) {
+        Consumer<? super DexClassAndMethod> onMethodTarget,
+        Consumer<? super LookupLambdaTarget> onLambdaTarget) {
       // Nothing to iterate for a failed lookup.
     }
+
+    @Override
+    public void forEachFailureDependency(
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+      // TODO: record and emit failure dependencies.
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index c2cb1d4..b425a2a 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -10,12 +13,9 @@
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.OptionalBool;
-import java.util.ArrayList;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
@@ -148,7 +148,9 @@
       DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
 
   public abstract LookupTarget lookupVirtualDispatchTarget(
-      LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo);
+      LambdaDescriptor lambdaInstance,
+      AppInfoWithClassHierarchy appInfo,
+      Consumer<? super DexEncodedMethod> methodCausingFailureConsumer);
 
   /** Result for a resolution that succeeds with a known declaration/definition. */
   public static class SingleResolutionResult extends MethodResolutionResult
@@ -431,46 +433,47 @@
         boolean isIncomplete =
             pinnedPredicate.isPinned(resolvedHolder) && pinnedPredicate.isPinned(resolvedMethod);
         return LookupResult.createResult(
-            Collections.singletonMap(
-                resolvedMethod, DexClassAndMethod.create(resolvedHolder, resolvedMethod)),
+            DexClassAndMethodSet.create(getResolutionPair()),
+            Collections.emptyList(),
             Collections.emptyList(),
             isIncomplete
                 ? LookupResultCollectionState.Incomplete
                 : LookupResultCollectionState.Complete);
       }
       assert resolvedMethod.isNonPrivateVirtualMethod();
-      Map<DexEncodedMethod, DexClassAndMethod> methodTargets = new IdentityHashMap<>();
-      List<LookupLambdaTarget> lambdaTargets = new ArrayList<>();
+      LookupResultSuccess.Builder resultBuilder = LookupResultSuccess.builder();
       LookupCompletenessHelper incompleteness = new LookupCompletenessHelper(pinnedPredicate);
       instantiatedInfo.forEachInstantiatedSubType(
           initialResolutionHolder.type,
           subClass -> {
             incompleteness.checkClass(subClass);
             DexClassAndMethod dexClassAndMethod =
-                lookupVirtualDispatchTarget(subClass, appInfo, resolvedHolder.type);
+                lookupVirtualDispatchTarget(
+                    subClass, appInfo, resolvedHolder.type, resultBuilder::addMethodCausingFailure);
             if (dexClassAndMethod != null) {
               incompleteness.checkDexClassAndMethod(dexClassAndMethod);
               addVirtualDispatchTarget(
-                  dexClassAndMethod, resolvedHolder.isInterface(), methodTargets);
+                  dexClassAndMethod, resolvedHolder.isInterface(), resultBuilder);
             }
           },
           lambda -> {
             assert resolvedHolder.isInterface()
                 || resolvedHolder.type == appInfo.dexItemFactory().objectType;
-            LookupTarget target = lookupVirtualDispatchTarget(lambda, appInfo);
+            LookupTarget target =
+                lookupVirtualDispatchTarget(
+                    lambda, appInfo, resultBuilder::addMethodCausingFailure);
             if (target != null) {
               if (target.isLambdaTarget()) {
-                lambdaTargets.add(target.asLambdaTarget());
+                resultBuilder.addLambdaTarget(target.asLambdaTarget());
               } else {
                 addVirtualDispatchTarget(
-                    target.asMethodTarget(), resolvedHolder.isInterface(), methodTargets);
+                    target.asMethodTarget(), resolvedHolder.isInterface(), resultBuilder);
               }
             }
           });
-      return LookupResult.createResult(
-          methodTargets,
-          lambdaTargets,
-          incompleteness.computeCollectionState(resolvedMethod.getReference(), appInfo));
+      return resultBuilder
+          .setState(incompleteness.computeCollectionState(resolvedMethod.getReference(), appInfo))
+          .build();
     }
 
     @Override
@@ -532,7 +535,7 @@
     private static void addVirtualDispatchTarget(
         DexClassAndMethod target,
         boolean holderIsInterface,
-        Map<DexEncodedMethod, DexClassAndMethod> result) {
+        LookupResultSuccess.Builder resultBuilder) {
       DexEncodedMethod targetMethod = target.getDefinition();
       assert !targetMethod.isPrivateMethod();
       if (holderIsInterface) {
@@ -559,17 +562,17 @@
         //   }
         //
         if (targetMethod.isDefaultMethod()) {
-          result.putIfAbsent(targetMethod, target);
+          resultBuilder.addMethodTarget(target);
         }
         // Default methods are looked up when looking at a specific subtype that does not override
         // them. Otherwise, we would look up default methods that are actually never used.
         // However, we have to add bridge methods, otherwise we can remove a bridge that will be
         // used.
         if (!targetMethod.accessFlags.isAbstract() && targetMethod.accessFlags.isBridge()) {
-          result.putIfAbsent(targetMethod, target);
+          resultBuilder.addMethodTarget(target);
         }
       } else {
-        result.putIfAbsent(targetMethod, target);
+        resultBuilder.addMethodTarget(target);
       }
     }
 
@@ -583,18 +586,21 @@
         InstantiatedObject instance, AppInfoWithClassHierarchy appInfo) {
       return instance.isClass()
           ? lookupVirtualDispatchTarget(instance.asClass(), appInfo)
-          : lookupVirtualDispatchTarget(instance.asLambda(), appInfo);
+          : lookupVirtualDispatchTarget(instance.asLambda(), appInfo, emptyConsumer());
     }
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
         DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
-      return lookupVirtualDispatchTarget(dynamicInstance, appInfo, initialResolutionHolder.type);
+      return lookupVirtualDispatchTarget(
+          dynamicInstance, appInfo, initialResolutionHolder.type, emptyConsumer());
     }
 
     @Override
     public LookupTarget lookupVirtualDispatchTarget(
-        LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo) {
+        LambdaDescriptor lambdaInstance,
+        AppInfoWithClassHierarchy appInfo,
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
       if (lambdaInstance.getMainMethod().match(resolvedMethod)) {
         DexMethod methodReference = lambdaInstance.implHandle.asMethod();
         DexClass holder = appInfo.definitionForHolder(methodReference);
@@ -605,11 +611,15 @@
         }
         return new LookupLambdaTarget(lambdaInstance, method);
       }
-      return lookupMaximallySpecificDispatchTarget(lambdaInstance, appInfo);
+      return lookupMaximallySpecificDispatchTarget(
+          lambdaInstance, appInfo, methodCausingFailureConsumer);
     }
 
     private DexClassAndMethod lookupVirtualDispatchTarget(
-        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo, DexType resolutionHolder) {
+        DexClass dynamicInstance,
+        AppInfoWithClassHierarchy appInfo,
+        DexType resolutionHolder,
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
       assert appInfo.isSubtype(dynamicInstance.type, resolutionHolder)
           : dynamicInstance.type + " is not a subtype of " + resolutionHolder;
       // TODO(b/148591377): Enable this assertion.
@@ -618,7 +628,7 @@
       if (resolvedMethod.isPrivateMethod()) {
         // If the resolved reference is private there is no dispatch.
         // This is assuming that the method is accessible, which implies self/nest access.
-        return DexClassAndMethod.create(resolvedHolder, resolvedMethod);
+        return getResolutionPair();
       }
       boolean allowPackageBlocked = resolvedMethod.accessFlags.isPackagePrivate();
       DexClass current = dynamicInstance;
@@ -645,17 +655,46 @@
       if (!resolvedHolder.isInterface()) {
         return null;
       }
-      return lookupMaximallySpecificDispatchTarget(dynamicInstance, appInfo);
+      return lookupMaximallySpecificDispatchTarget(
+          dynamicInstance, appInfo, methodCausingFailureConsumer);
     }
 
     private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
-        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
-      return appInfo.lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.getReference());
+        DexClass dynamicInstance,
+        AppInfoWithClassHierarchy appInfo,
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+      MethodResolutionResult maximallySpecificResolutionResult =
+          appInfo.resolveMaximallySpecificTarget(dynamicInstance, resolvedMethod.getReference());
+      if (maximallySpecificResolutionResult.isSingleResolution()) {
+        return maximallySpecificResolutionResult.getResolutionPair();
+      }
+      if (maximallySpecificResolutionResult.isFailedResolution()) {
+        maximallySpecificResolutionResult
+            .asFailedResolution()
+            .forEachFailureDependency(methodCausingFailureConsumer);
+        return null;
+      }
+      assert maximallySpecificResolutionResult.isArrayCloneMethodResult();
+      return null;
     }
 
     private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
-        LambdaDescriptor lambdaDescriptor, AppInfoWithClassHierarchy appInfo) {
-      return appInfo.lookupMaximallySpecificMethod(lambdaDescriptor, resolvedMethod.getReference());
+        LambdaDescriptor lambdaDescriptor,
+        AppInfoWithClassHierarchy appInfo,
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+      MethodResolutionResult maximallySpecificResolutionResult =
+          appInfo.resolveMaximallySpecificTarget(lambdaDescriptor, resolvedMethod.getReference());
+      if (maximallySpecificResolutionResult.isSingleResolution()) {
+        return maximallySpecificResolutionResult.getResolutionPair();
+      }
+      if (maximallySpecificResolutionResult.isFailedResolution()) {
+        maximallySpecificResolutionResult
+            .asFailedResolution()
+            .forEachFailureDependency(methodCausingFailureConsumer);
+        return null;
+      }
+      assert maximallySpecificResolutionResult.isArrayCloneMethodResult();
+      return null;
     }
 
     /**
@@ -773,7 +812,9 @@
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo) {
+        LambdaDescriptor lambdaInstance,
+        AppInfoWithClassHierarchy appInfo,
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
       return null;
     }
   }
@@ -823,7 +864,8 @@
       return this;
     }
 
-    public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
+    public void forEachFailureDependency(
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
       // Default failure has no dependencies.
     }
 
@@ -871,7 +913,8 @@
     }
 
     @Override
-    public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
+    public void forEachFailureDependency(
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
       this.methodsCausingError.forEach(methodCausingFailureConsumer);
     }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
index 72990cb..07c487f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -130,7 +130,7 @@
                   if (resolutionResult.isFailedResolution()) {
                     // TODO(b/190154391): Do we need to propagate argument information to the first
                     //  virtual method above the inaccessible method in the class hierarchy?
-                    assert resolutionResult.isIllegalAccessErrorResult(subclass, appView.appInfo());
+                    assert resolutionResult.asFailedResolution().hasMethodsCausingError();
                     return;
                   }
 
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 c4e0518..52d59bb 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -10,6 +10,7 @@
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 import static java.util.Collections.emptySet;
 
 import com.android.tools.r8.Diagnostic;
@@ -63,6 +64,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.InvalidCode;
 import com.android.tools.r8.graph.LookupLambdaTarget;
+import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.MethodResolutionResult;
@@ -378,7 +380,7 @@
   private final ProgramMethodSet pendingReflectiveUses = ProgramMethodSet.createLinked();
 
   /** Mapping of types to the resolved methods for that type along with the context. */
-  private final Map<DexProgramClass, Map<ResolutionSearchKey, Set<DexProgramClass>>>
+  private final Map<DexProgramClass, Map<ResolutionSearchKey, ProgramMethodSet>>
       reachableVirtualTargets = new IdentityHashMap<>();
 
   /** Collection of keep requirements for the program. */
@@ -2460,7 +2462,7 @@
     }
   }
 
-  private Map<ResolutionSearchKey, Set<DexProgramClass>> getReachableVirtualTargets(
+  private Map<ResolutionSearchKey, ProgramMethodSet> getReachableVirtualTargets(
       DexProgramClass clazz) {
     return reachableVirtualTargets.getOrDefault(clazz, Collections.emptyMap());
   }
@@ -2480,26 +2482,46 @@
                 assert false : "Should not be null";
                 return;
               }
-              contexts.forEach(
-                  context ->
-                      singleResolution
-                          .lookupVirtualDispatchTargets(
-                              context,
-                              appInfo,
-                              (type, subTypeConsumer, lambdaConsumer) -> {
-                                assert appInfo.isSubtype(currentClass.type, type);
-                                instantiation.apply(subTypeConsumer, lambdaConsumer);
-                              },
-                              definition ->
-                                  keepInfo.isPinned(definition.getReference(), appInfo, options))
-                          .forEach(
-                              target ->
-                                  markVirtualDispatchTargetAsLive(
-                                      target,
-                                      programMethod ->
-                                          graphReporter.reportReachableMethodAsLive(
-                                              singleResolution.getResolvedMethod().getReference(),
-                                              programMethod))));
+              Map<DexProgramClass, List<ProgramMethod>> contextsByClass = new IdentityHashMap<>();
+              for (ProgramMethod context : contexts) {
+                contextsByClass
+                    .computeIfAbsent(context.getHolder(), ignoreKey(ArrayList::new))
+                    .add(context);
+              }
+              contextsByClass.forEach(
+                  (contextHolder, contextsInHolder) -> {
+                    LookupResult lookupResult =
+                        singleResolution.lookupVirtualDispatchTargets(
+                            contextHolder,
+                            appInfo,
+                            (type, subTypeConsumer, lambdaConsumer) -> {
+                              assert appInfo.isSubtype(currentClass.type, type);
+                              instantiation.apply(subTypeConsumer, lambdaConsumer);
+                            },
+                            definition ->
+                                keepInfo.isPinned(definition.getReference(), appInfo, options));
+                    lookupResult.forEach(
+                        target ->
+                            markVirtualDispatchTargetAsLive(
+                                target,
+                                programMethod ->
+                                    graphReporter.reportReachableMethodAsLive(
+                                        singleResolution.getResolvedMethod().getReference(),
+                                        programMethod)));
+                    lookupResult.forEachFailureDependency(
+                        method -> {
+                          DexProgramClass clazz =
+                              getProgramClassOrNull(method.getHolderType(), contextHolder);
+                          if (clazz != null) {
+                            failedMethodResolutionTargets.add(method.getReference());
+                            for (ProgramMethod context : contextsInHolder) {
+                              markMethodAsTargeted(
+                                  new ProgramMethod(clazz, method),
+                                  KeepReason.invokedFrom(context));
+                            }
+                          }
+                        });
+                  });
             });
   }
 
@@ -2886,7 +2908,7 @@
   }
 
   private void markVirtualMethodAsReachable(
-      DexMethod method, boolean interfaceInvoke, ProgramDefinition context, KeepReason reason) {
+      DexMethod method, boolean interfaceInvoke, ProgramMethod context, KeepReason reason) {
     if (method.holder.isArrayType()) {
       // This is an array type, so the actual class will be generated at runtime. We treat this
       // like an invoke on a direct subtype of java.lang.Object that has no further subtypes.
@@ -2924,9 +2946,9 @@
     // If the method has already been marked, just report the new reason for the resolved target and
     // save the context to ensure correct lookup of virtual dispatch targets.
     ResolutionSearchKey resolutionSearchKey = new ResolutionSearchKey(method, interfaceInvoke);
-    Set<DexProgramClass> seenContexts = getReachableVirtualTargets(holder).get(resolutionSearchKey);
+    ProgramMethodSet seenContexts = getReachableVirtualTargets(holder).get(resolutionSearchKey);
     if (seenContexts != null) {
-      seenContexts.add(contextHolder);
+      seenContexts.add(context);
       graphReporter.registerMethod(resolution.getResolvedMethod(), reason);
       return;
     }
@@ -2948,8 +2970,8 @@
     // The method resolved and is accessible, so currently live overrides become live.
     reachableVirtualTargets
         .computeIfAbsent(holder, ignoreArgument(HashMap::new))
-        .computeIfAbsent(resolutionSearchKey, ignoreArgument(Sets::newIdentityHashSet))
-        .add(contextHolder);
+        .computeIfAbsent(resolutionSearchKey, ignoreArgument(ProgramMethodSet::create))
+        .add(context);
 
     resolution
         .lookupVirtualDispatchTargets(
@@ -4603,7 +4625,7 @@
             virtualMethod -> {
               keepInfo.joinMethod(
                   virtualMethod, joiner -> joiner.disallowOptimization().disallowShrinking());
-              markVirtualMethodAsReachable(virtualMethod.getReference(), true, clazz, reason);
+              markVirtualMethodAsReachable(virtualMethod.getReference(), true, method, reason);
             });
       }
     }
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java b/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
index 7d47371..a5acf15 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
@@ -33,7 +33,7 @@
         .addProgramClasses(Main.class, I.class, J.class)
         .addProgramClassFileData(getProgramClassFileData())
         .run(parameters.getRuntime(), Main.class)
-        .apply(runResult -> inspectRunResult(runResult, false));
+        .apply(this::inspectRunResult);
   }
 
   @Test
@@ -45,12 +45,11 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        .apply(runResult -> inspectRunResult(runResult, true));
+        .apply(this::inspectRunResult);
   }
 
-  private void inspectRunResult(TestRunResult<?> runResult, boolean isR8) {
-    if (parameters.isCfRuntime(CfVm.JDK11)
-        || (isR8 && parameters.canUseDefaultAndStaticInterfaceMethods())) {
+  private void inspectRunResult(TestRunResult<?> runResult) {
+    if (parameters.isCfRuntime(CfVm.JDK11)) {
       runResult.assertFailureWithErrorThatThrows(AbstractMethodError.class);
     } else {
       runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);