Mark methods as library overrides in a final pass after enqueuing

Bug: b/259531498
Change-Id: Icb58755c739ff030170b35906f051af7a42f527f
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathOrLibraryClass.java b/src/main/java/com/android/tools/r8/graph/ClasspathOrLibraryClass.java
index d666539..78de6f7 100644
--- a/src/main/java/com/android/tools/r8/graph/ClasspathOrLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathOrLibraryClass.java
@@ -15,4 +15,10 @@
   static ClasspathOrLibraryClass asClasspathOrLibraryClass(DexClass clazz) {
     return clazz != null ? clazz.asClasspathOrLibraryClass() : null;
   }
+
+  DexEncodedMethod lookupMethod(DexMethod method);
+
+  DexEncodedMethod lookupVirtualMethod(DexMethod method);
+
+  Iterable<DexEncodedMethod> virtualMethods();
 }
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 8c949fe..ef7586a 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -183,6 +183,12 @@
   public abstract LookupMethodTarget lookupVirtualDispatchTarget(
       DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
 
+  public abstract LookupMethodTarget lookupVirtualDispatchTarget(
+      DexClass dynamicInstance,
+      AppInfoWithClassHierarchy appInfo,
+      Consumer<DexType> typeCausingFailureConsumer,
+      Consumer<? super DexEncodedMethod> methodCausingFailureConsumer);
+
   public abstract LookupTarget lookupVirtualDispatchTarget(
       LambdaDescriptor lambdaInstance,
       AppInfoWithClassHierarchy appInfo,
@@ -548,9 +554,9 @@
                 lookupVirtualDispatchTarget(
                     subClass,
                     appInfo,
-                    resolvedHolder.type,
                     resultBuilder::addTypeCausingFailure,
-                    resultBuilder::addMethodCausingFailure);
+                    resultBuilder::addMethodCausingFailure,
+                    resolvedHolder.type);
             if (lookupTarget != null) {
               incompleteness.checkDexClassAndMethod(lookupTarget);
               addVirtualDispatchTarget(lookupTarget, resolvedHolder.isInterface(), resultBuilder);
@@ -698,7 +704,21 @@
     public LookupMethodTarget lookupVirtualDispatchTarget(
         DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return lookupVirtualDispatchTarget(
-          dynamicInstance, appInfo, initialResolutionHolder.type, emptyConsumer(), emptyConsumer());
+          dynamicInstance, appInfo, emptyConsumer(), emptyConsumer());
+    }
+
+    @Override
+    public LookupMethodTarget lookupVirtualDispatchTarget(
+        DexClass dynamicInstance,
+        AppInfoWithClassHierarchy appInfo,
+        Consumer<DexType> typeCausingFailureConsumer,
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+      return lookupVirtualDispatchTarget(
+          dynamicInstance,
+          appInfo,
+          typeCausingFailureConsumer,
+          methodCausingFailureConsumer,
+          initialResolutionHolder.type);
     }
 
     @Override
@@ -724,9 +744,9 @@
     private LookupMethodTarget lookupVirtualDispatchTarget(
         DexClass dynamicInstance,
         AppInfoWithClassHierarchy appInfo,
-        DexType resolutionHolder,
         Consumer<DexType> typeCausingFailureConsumer,
-        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer,
+        DexType resolutionHolder) {
       assert appInfo.isSubtype(dynamicInstance.type, resolutionHolder)
           : dynamicInstance.type + " is not a subtype of " + resolutionHolder;
       // TODO(b/148591377): Enable this assertion.
@@ -1030,6 +1050,15 @@
     }
 
     @Override
+    public LookupMethodTarget lookupVirtualDispatchTarget(
+        DexClass dynamicInstance,
+        AppInfoWithClassHierarchy appInfo,
+        Consumer<DexType> typeCausingFailureConsumer,
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+      return null;
+    }
+
+    @Override
     public LookupTarget lookupVirtualDispatchTarget(
         LambdaDescriptor lambdaInstance,
         AppInfoWithClassHierarchy appInfo,
@@ -1411,6 +1440,15 @@
     }
 
     @Override
+    public DexClassAndMethod lookupVirtualDispatchTarget(
+        DexClass dynamicInstance,
+        AppInfoWithClassHierarchy appInfo,
+        Consumer<DexType> typeCausingFailureConsumer,
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
     public LookupTarget lookupVirtualDispatchTarget(
         LambdaDescriptor lambdaInstance,
         AppInfoWithClassHierarchy appInfo,
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerLibraryOverrideAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerLibraryOverrideAnalysis.java
new file mode 100644
index 0000000..5b84a81
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerLibraryOverrideAnalysis.java
@@ -0,0 +1,313 @@
+// Copyright (c) 2022, 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.graph.analysis;
+
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupMethodTarget;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.StatefulDepthFirstSearchWorkList;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+public class EnqueuerLibraryOverrideAnalysis extends EnqueuerAnalysis {
+
+  private final Set<ClasspathOrLibraryClass> setWithSingleJavaLangObject;
+
+  private final Map<DexClass, LibraryMethodOverridesInHierarchy> libraryClassesInHierarchyCache =
+      new IdentityHashMap<>();
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+  public EnqueuerLibraryOverrideAnalysis(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.appView = appView;
+    DexClass javaLangObjectClass = appView.definitionFor(appView.dexItemFactory().objectType);
+    if (javaLangObjectClass != null && javaLangObjectClass.isNotProgramClass()) {
+      setWithSingleJavaLangObject =
+          ImmutableSet.of(javaLangObjectClass.asClasspathOrLibraryClass());
+    } else {
+      setWithSingleJavaLangObject = Collections.emptySet();
+    }
+  }
+
+  @Override
+  public void done(Enqueuer enqueuer) {
+    Set<DexProgramClass> liveProgramTypes = enqueuer.getLiveProgramTypes();
+    ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection =
+        enqueuer.getObjectAllocationInfoCollection();
+    liveProgramTypes.forEach(
+        liveProgramType -> {
+          if (!objectAllocationInfoCollection.isInstantiatedDirectly(liveProgramType)) {
+            return;
+          }
+          // Abstract classes cannot be instantiated.
+          if (liveProgramType.isAbstract()) {
+            return;
+          }
+          markLibraryOverridesForProgramClass(liveProgramType);
+        });
+  }
+
+  private void markLibraryOverridesForProgramClass(DexProgramClass programClass) {
+    LibraryMethodOverridesInHierarchy libraryMethodOverridesInHierarchy =
+        ensureLibraryClassesPopulatedForProgramClass(programClass);
+    AppInfoWithClassHierarchy appInfoWithClassHierarchy = appView.appInfo();
+    libraryMethodOverridesInHierarchy.forEachOverriddenMethod(
+        (libraryClass, method) ->
+            appInfoWithClassHierarchy
+                .resolveMethodOn(libraryClass.asDexClass(), method.getReference())
+                .forEachMethodResolutionResult(
+                    result -> {
+                      LookupMethodTarget lookupTarget =
+                          result.lookupVirtualDispatchTarget(
+                              programClass,
+                              appInfoWithClassHierarchy,
+                              emptyConsumer(),
+                              failingMethod -> {
+                                DexClass holderClass =
+                                    appInfoWithClassHierarchy.definitionFor(
+                                        failingMethod.getContextType());
+                                if (holderClass != null && holderClass.isProgramClass()) {
+                                  failingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+                                }
+                              });
+                      if (lookupTarget != null) {
+                        DexEncodedMethod definition = lookupTarget.getDefinition();
+                        definition.setLibraryMethodOverride(OptionalBool.TRUE);
+                      }
+                    }));
+  }
+
+  /***
+   * Populating the library state for an instantiated program class is done by a DFS
+   * algorithm where we compute all library classes in the hierarchy and then, when doing backtracking, we check if a program method has a corresponding
+   * definition in one of its library classes. If a program method potentially overrides a library
+   * method we keep track of it for simulating virtual dispatch on it later.
+   *
+   * A special care has to be taken here when interfaces contributing to classes later in the
+   * hierarchy than the override:
+   * <pre>
+   * class ProgramA {
+   *   foo() {...}
+   * }
+   *
+   * interface Lib {
+   *   foo();
+   * }
+   *
+   * class ProgramB extends ProgramA implements Lib { }
+   * </pre>
+   *
+   * This pattern does not satisfy the DFS algorithm since ProgramB do not directly override any
+   * methods in Lib. As a consequence we always consider all interface methods to be overridden. In
+   * general this is almost always the case since we do not see many default library methods in the
+   * library.
+   *
+   * @return the computed library method override state for this program class.
+   */
+  private LibraryMethodOverridesInHierarchy ensureLibraryClassesPopulatedForProgramClass(
+      DexProgramClass clazz) {
+    new StatefulDepthFirstSearchWorkList<DexClass, LibraryMethodOverridesInHierarchy, Void>() {
+
+      @Override
+      @SuppressWarnings("ReturnValueIgnored")
+      protected TraversalContinuation<Void, LibraryMethodOverridesInHierarchy> process(
+          DFSNodeWithState<DexClass, LibraryMethodOverridesInHierarchy> node,
+          Function<DexClass, DFSNodeWithState<DexClass, LibraryMethodOverridesInHierarchy>>
+              childNodeConsumer) {
+        DexClass clazz = node.getNode();
+        LibraryMethodOverridesInHierarchy libraryMethodOverridesInHierarchy =
+            libraryClassesInHierarchyCache.get(clazz);
+        if (libraryMethodOverridesInHierarchy != null) {
+          node.setState(libraryMethodOverridesInHierarchy);
+        } else {
+          clazz.forEachImmediateSupertype(
+              superType ->
+                  appView
+                      .contextIndependentDefinitionForWithResolutionResult(superType)
+                      .forEachClassResolutionResult(childNodeConsumer::apply));
+        }
+        return TraversalContinuation.doContinue();
+      }
+
+      @Override
+      protected TraversalContinuation<Void, LibraryMethodOverridesInHierarchy> joiner(
+          DFSNodeWithState<DexClass, LibraryMethodOverridesInHierarchy> node,
+          List<DFSNodeWithState<DexClass, LibraryMethodOverridesInHierarchy>> childStates) {
+        DexClass clazz = node.getNode();
+        if (node.getState() == null) {
+          LibraryMethodOverridesInHierarchy.Builder builder =
+              LibraryMethodOverridesInHierarchy.builder();
+          childStates.forEach(childState -> builder.addParentState(childState.getState()));
+          if (clazz.isNotProgramClass()) {
+            builder.addLibraryClass(clazz.asClasspathOrLibraryClass());
+          }
+          builder.buildLibraryClasses(setWithSingleJavaLangObject);
+          if (clazz.isProgramClass()) {
+            clazz
+                .asProgramClass()
+                .virtualProgramMethods()
+                .forEach(builder::addLibraryOverridesForMethod);
+          }
+          LibraryMethodOverridesInHierarchy newState = builder.build();
+          node.setState(newState);
+          LibraryMethodOverridesInHierarchy oldState =
+              libraryClassesInHierarchyCache.put(clazz, newState);
+          assert oldState == null;
+        }
+        return TraversalContinuation.doContinue();
+      }
+    }.run(clazz);
+
+    return libraryClassesInHierarchyCache.get(clazz);
+  }
+
+  /***
+   * The LibraryMethodOverridesInHierarchy keeps track of the library classes and the potential
+   * overridden library methods for each class in the hierarchy of a class. The libraryClasses are
+   * a flattened list of all library classes that exists in the hierarchy and the overriden library
+   * methods is a map of methods that may be overridden for the current class.
+   */
+  private static class LibraryMethodOverridesInHierarchy {
+
+    private final List<LibraryMethodOverridesInHierarchy> parentStates;
+    private final Set<ClasspathOrLibraryClass> libraryClasses;
+    private final Map<DexEncodedMethod, ClasspathOrLibraryClass> overriddenLibraryMethods;
+
+    private LibraryMethodOverridesInHierarchy(
+        List<LibraryMethodOverridesInHierarchy> parentStates,
+        Set<ClasspathOrLibraryClass> libraryClasses,
+        Map<DexEncodedMethod, ClasspathOrLibraryClass> overriddenLibraryMethods) {
+      this.parentStates = parentStates;
+      this.libraryClasses = libraryClasses;
+      this.overriddenLibraryMethods = overriddenLibraryMethods;
+    }
+
+    public void forEachOverriddenMethod(
+        BiConsumer<ClasspathOrLibraryClass, DexEncodedMethod> consumer) {
+      forEachOverriddenMethod(consumer, Sets.newIdentityHashSet());
+    }
+
+    private void forEachOverriddenMethod(
+        BiConsumer<ClasspathOrLibraryClass, DexEncodedMethod> consumer,
+        Set<DexEncodedMethod> seen) {
+      overriddenLibraryMethods.forEach(
+          (method, clazz) -> {
+            if (seen.add(method)) {
+              consumer.accept(clazz, method);
+            }
+          });
+      parentStates.forEach(parentState -> parentState.forEachOverriddenMethod(consumer, seen));
+    }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+
+    public static class Builder {
+
+      private Map<DexEncodedMethod, ClasspathOrLibraryClass> overriddenLibraryMethods;
+      private Set<ClasspathOrLibraryClass> libraryClasses;
+      private final List<LibraryMethodOverridesInHierarchy> parentStates = new ArrayList<>();
+      private boolean hasBuildLibraryClasses = false;
+
+      public void addParentState(LibraryMethodOverridesInHierarchy state) {
+        assert !hasBuildLibraryClasses;
+        parentStates.add(state);
+      }
+
+      public void addLibraryClass(ClasspathOrLibraryClass libraryClass) {
+        ensureLibraryClasses();
+        libraryClasses.add(libraryClass);
+        if (libraryClass.isInterface()) {
+          ensureOverriddenLibraryMethods();
+          libraryClass
+              .virtualMethods()
+              .forEach(
+                  virtualMethod ->
+                      overriddenLibraryMethods.putIfAbsent(virtualMethod, libraryClass));
+        }
+      }
+
+      private void ensureLibraryClasses() {
+        if (libraryClasses == null) {
+          libraryClasses = new LinkedHashSet<>();
+        }
+      }
+
+      private void buildLibraryClasses(Set<ClasspathOrLibraryClass> setWithSingleJavaLangObject) {
+        if (libraryClasses != null) {
+          parentStates.forEach(parentState -> libraryClasses.addAll(parentState.libraryClasses));
+        } else if (parentStates.size() == 1) {
+          libraryClasses = parentStates.get(0).libraryClasses;
+        } else {
+          libraryClasses = new LinkedHashSet<>();
+          for (LibraryMethodOverridesInHierarchy parentState : parentStates) {
+            libraryClasses.addAll(parentState.libraryClasses);
+          }
+          if (libraryClasses.size() == 1) {
+            assert libraryClasses.equals(setWithSingleJavaLangObject);
+            libraryClasses = setWithSingleJavaLangObject;
+          }
+        }
+        hasBuildLibraryClasses = true;
+      }
+
+      private void addLibraryOverridesForMethod(ProgramMethod method) {
+        assert hasBuildLibraryClasses;
+        DexMethod reference = method.getReference();
+        libraryClasses.forEach(
+            libraryClass -> {
+              if (libraryClass.isInterface()) {
+                // All interface methods are already added when visiting.
+                return;
+              }
+              DexEncodedMethod libraryMethod = libraryClass.lookupVirtualMethod(reference);
+              if (libraryMethod != null) {
+                addOverriddenMethod(libraryClass, libraryMethod);
+              }
+            });
+      }
+
+      private void addOverriddenMethod(ClasspathOrLibraryClass clazz, DexEncodedMethod method) {
+        ensureOverriddenLibraryMethods();
+        ClasspathOrLibraryClass existing = overriddenLibraryMethods.put(method, clazz);
+        assert existing == null;
+      }
+
+      private void ensureOverriddenLibraryMethods() {
+        if (overriddenLibraryMethods == null) {
+          overriddenLibraryMethods = new IdentityHashMap<>();
+        }
+      }
+
+      public LibraryMethodOverridesInHierarchy build() {
+        return new LibraryMethodOverridesInHierarchy(
+            parentStates,
+            libraryClasses,
+            overriddenLibraryMethods == null ? Collections.emptyMap() : overriddenLibraryMethods);
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
index 94bd06b..40d34cb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -208,9 +208,7 @@
             .generateCfCode();
     DexEncodedMethod newMethod = wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode);
     newMethod.setCode(cfCode, DexEncodedMethod.NO_PARAMETER_INFO);
-    if (method.getDefinition().isLibraryMethodOverride().isTrue()) {
-      newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
-    }
+    newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     ProgramMethod callback = new ProgramMethod(clazz, newMethod);
     assert eventConsumer != null;
     eventConsumer.acceptAPIConversionCallback(callback);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
index 5f0bea9..5377ecb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -608,11 +608,6 @@
       }
 
       @Override
-      protected List<Void> getFinalStateForRoots(Collection<? extends StringBuilderNode> roots) {
-        return null;
-      }
-
-      @Override
       public TraversalContinuation<Void, Void> joiner(DFSNode<StringBuilderNode> node) {
         StringBuilderNode node1 = node.getNode();
         processingOrder.add(node1);
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 173c302..c7a45bc 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -95,6 +95,7 @@
 import com.android.tools.r8.graph.analysis.EnqueuerFieldAccessAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerLibraryOverrideAnalysis;
 import com.android.tools.r8.graph.analysis.GetArrayOfMissingTypeVerifyErrorWorkaround;
 import com.android.tools.r8.graph.analysis.InvokeVirtualToInterfaceVerifyErrorWorkaround;
 import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
@@ -535,6 +536,10 @@
     return graphReporter;
   }
 
+  public Set<DexProgramClass> getLiveProgramTypes() {
+    return liveTypes.getItems();
+  }
+
   private EnqueuerUseRegistryFactory createUseRegistryFactory() {
     if (mode.isFinalTreeShaking()) {
       return appView.withGeneratedMessageLiteShrinker(
@@ -679,17 +684,6 @@
     return definitionFor(type, context, this::recordNonProgramClass, this::reportMissingClass);
   }
 
-  public DexLibraryClass definitionForLibraryClassOrIgnore(DexType type) {
-    assert type.isClassType();
-    ClassResolutionResult classResolutionResult =
-        appInfo().contextIndependentDefinitionForWithResolutionResult(type);
-    return classResolutionResult.hasClassResolutionResult()
-            && !classResolutionResult.isMultipleClassResolutionResult()
-        ? DexLibraryClass.asLibraryClassOrNull(
-            classResolutionResult.toSingleClassWithProgramOverLibrary())
-        : null;
-  }
-
   public boolean hasAlternativeLibraryDefinition(DexProgramClass programClass) {
     ClassResolutionResult classResolutionResult =
         internalDefinitionFor(
@@ -2869,10 +2863,7 @@
             } else {
               markLibraryAndClasspathMethodOverridesAsLive(instantiation, clazz);
             }
-            if (clazz.superType != null) {
-              worklist.addIfNotSeen(clazz.superType);
-            }
-            worklist.addIfNotSeen(clazz.interfaces);
+            clazz.forEachImmediateSupertype(worklist::addIfNotSeen);
           });
     }
   }
@@ -2969,7 +2960,7 @@
       // class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
       // In the first enqueuer phase, the signature has not been desugared, so firstResolution
       // maintains the library override. In the second enqueuer phase, the signature has been
-      // desugared, and the second resolution maintains the the library override.
+      // desugared, and the second resolution maintains the library override.
       if (instantiation.isClass()
           && appView.typeRewriter.hasRewrittenTypeInSignature(
               method.getReference().proto, appView)) {
@@ -3001,11 +2992,6 @@
         method ->
             graphReporter.reportLibraryMethodAsLive(
                 instantiation, method, libraryOrClasspathClass));
-    if (instantiation.isClass()) {
-      // TODO(b/149976493): We need to mark these for lambdas too!
-      markOverridesAsLibraryMethodOverrides(
-          instantiation.asClass(), lookup.asMethodTarget().getDefinition().getReference());
-    }
   }
 
   private void markOverridesAsLibraryMethodOverrides(
@@ -3618,6 +3604,9 @@
     if (options.apiModelingOptions().enableLibraryApiModeling) {
       registerAnalysis(new ApiModelAnalysis(appView));
     }
+    if (!mode.isMainDexTracing()) {
+      registerAnalysis(new EnqueuerLibraryOverrideAnalysis(appView));
+    }
 
     // Transfer the minimum keep info from the root set into the Enqueuer state.
     includeMinimumKeepInfo(rootSet);
@@ -3640,8 +3629,8 @@
     enqueueAllIfNotShrinking();
     trace(executorService, timing);
     options.reporter.failIfPendingErrors();
-    finalizeLibraryMethodOverrideInformation();
     analyses.forEach(analyses -> analyses.done(this));
+    finalizeLibraryMethodOverrideInformation();
     assert verifyKeptGraph();
     if (mode.isInitialTreeShaking() && forceProguardCompatibility) {
       appView.setProguardCompatibilityActions(proguardCompatibilityActionsBuilder.build());
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultipleOnCompleteTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultipleOnCompleteTest.java
index e220b6e..f452c4b 100644
--- a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultipleOnCompleteTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultipleOnCompleteTest.java
@@ -139,7 +139,6 @@
     @Override
     default void foo() {
       System.out.println("J_Program::foo");
-      ;
     }
   }
 
@@ -147,7 +146,6 @@
     @Override
     default void foo() {
       System.out.println("J_Library::foo");
-      ;
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
index 42268b2..ac11d72 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.OptionalBool;
 import java.util.AbstractList;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,17 +50,17 @@
   private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
     DexItemFactory dexItemFactory = appInfo.dexItemFactory();
     verifyIsEmptyMarkedAsOverridingLibraryMethod(
-        appInfo, dexItemFactory.createType(descriptor(A.class)));
+        appInfo, dexItemFactory.createType(descriptor(A.class)), OptionalBool.TRUE);
     verifyIsEmptyMarkedAsOverridingLibraryMethod(
-        appInfo, dexItemFactory.createType(descriptor(I.class)));
+        appInfo, dexItemFactory.createType(descriptor(I.class)), OptionalBool.FALSE);
   }
 
   private void verifyIsEmptyMarkedAsOverridingLibraryMethod(
-      AppInfoWithLiveness appInfo, DexType type) {
+      AppInfoWithLiveness appInfo, DexType type, OptionalBool expected) {
     DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
     DexEncodedMethod method =
         clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("isEmpty"));
-    assertTrue(method.isLibraryMethodOverride().isTrue());
+    assertEquals(expected, method.isLibraryMethodOverride());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
index 563a312..928a2fc 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.shaking;
 
-import static org.junit.Assert.assertTrue;
+import static com.android.tools.r8.CollectorsUtils.toSingle;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.Enqueuer.Mode;
+import com.android.tools.r8.utils.OptionalBool;
 import java.util.Iterator;
 import java.util.Spliterator;
 import java.util.function.Consumer;
@@ -52,21 +54,29 @@
   private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Mode mode) {
     DexItemFactory dexItemFactory = appInfo.dexItemFactory();
     verifyIteratorMethodMarkedAsOverridingLibraryMethod(
-        appInfo, dexItemFactory.createType(descriptor(I.class)));
+        appInfo, dexItemFactory.createType(descriptor(I.class)), OptionalBool.FALSE);
     verifyIteratorMethodMarkedAsOverridingLibraryMethod(
-        appInfo, dexItemFactory.createType(descriptor(J.class)));
+        appInfo, dexItemFactory.createType(descriptor(J.class)), OptionalBool.FALSE);
+    if (parameters.isDexRuntime()) {
+      DexProgramClass lambda =
+          appInfo.classes().stream()
+              .filter(x -> x.toSourceString().contains("$InternalSyntheticLambda"))
+              .collect(toSingle());
+      verifyIteratorMethodMarkedAsOverridingLibraryMethod(
+          appInfo, lambda.getType(), OptionalBool.TRUE);
+    }
   }
 
   private void verifyIteratorMethodMarkedAsOverridingLibraryMethod(
-      AppInfoWithLiveness appInfo, DexType type) {
+      AppInfoWithLiveness appInfo, DexType type, OptionalBool expected) {
     DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
     DexEncodedMethod method =
         clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("iterator"));
     // TODO(b/149976493): Mark library overrides from lambda instances.
     if (parameters.isCfRuntime()) {
-      assertTrue(method.isLibraryMethodOverride().isFalse());
+      assertEquals(OptionalBool.FALSE, method.isLibraryMethodOverride());
     } else {
-      assertTrue(method.isLibraryMethodOverride().isTrue());
+      assertEquals(expected, method.isLibraryMethodOverride());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
index a7908c2..188d4aa 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
@@ -6,7 +6,6 @@
 
 import static com.google.common.base.Predicates.alwaysTrue;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -16,6 +15,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.OptionalBool;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -49,17 +49,17 @@
   private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
     DexItemFactory dexItemFactory = appInfo.dexItemFactory();
     verifySingleVirtualMethodMarkedAsOverridingLibraryMethod(
-        appInfo, dexItemFactory.createType(descriptor(A.class)));
+        appInfo, dexItemFactory.createType(descriptor(A.class)), OptionalBool.FALSE);
     verifySingleVirtualMethodMarkedAsOverridingLibraryMethod(
-        appInfo, dexItemFactory.createType(descriptor(B.class)));
+        appInfo, dexItemFactory.createType(descriptor(B.class)), OptionalBool.TRUE);
   }
 
   private void verifySingleVirtualMethodMarkedAsOverridingLibraryMethod(
-      AppInfoWithLiveness appInfo, DexType type) {
+      AppInfoWithLiveness appInfo, DexType type, OptionalBool expected) {
     DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
     assertEquals(1, clazz.getMethodCollection().numberOfVirtualMethods());
     DexEncodedMethod method = clazz.lookupVirtualMethod(alwaysTrue());
-    assertTrue(method.isLibraryMethodOverride().isTrue());
+    assertEquals(expected, method.isLibraryMethodOverride());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideAbstractTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideAbstractTest.java
new file mode 100644
index 0000000..c7f3e23
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideAbstractTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.librarymethodoverride;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideAbstractTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(SubProgramClass.class, Main.class)
+        .addProgramClassFileData(
+            transformer(ProgramClass.class).removeMethodsWithName("foo").transform())
+        .addLibraryClasses(LibraryClass.class)
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(AbstractMethodError.class);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(SubProgramClass.class, Main.class)
+        .addProgramClassFileData(
+            transformer(ProgramClass.class).removeMethodsWithName("foo").transform())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryClasses(LibraryClass.class)
+        .addOptionsModification(
+            options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .compile()
+        .addBootClasspathClasses(LibraryClass.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(AbstractMethodError.class);
+  }
+
+  private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
+    if (mode.isInitialTreeShaking()) {
+      DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+      DexProgramClass clazz =
+          appInfo
+              .definitionFor(dexItemFactory.createType(descriptor(SubProgramClass.class)))
+              .asProgramClass();
+      DexEncodedMethod method =
+          clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
+      assertTrue(method.isLibraryMethodOverride().isTrue());
+    }
+  }
+
+  public abstract static class LibraryClass {
+
+    abstract void foo();
+
+    public static void callFoo(LibraryClass libraryClass) {
+      libraryClass.foo();
+    }
+  }
+
+  public abstract static class SubProgramClass extends LibraryClass {
+
+    @Override
+    abstract void foo();
+  }
+
+  public static class ProgramClass extends SubProgramClass {
+
+    @Override
+    void foo() {
+      throw new RuntimeException("Should have been removed.");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      LibraryClass.callFoo(new ProgramClass());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodICCETest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodICCETest.java
new file mode 100644
index 0000000..aa8ca52
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodICCETest.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.librarymethodoverride;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideDefaultMethodICCETest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, ProgramI.class, ProgramJ.class)
+        .addProgramClassFileData(
+            transformer(ProgramClass.class)
+                .setImplements(LibraryInterface.class, ProgramI.class, ProgramJ.class)
+                .transform())
+        .addLibraryClasses(LibraryInterface.class, LibraryCaller.class)
+        .addRunClasspathFiles(
+            buildOnDexRuntime(parameters, LibraryInterface.class, LibraryCaller.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrowsIf(
+            parameters.isCfRuntime() && parameters.getRuntime().asCf().isOlderThan(CfVm.JDK11),
+            IncompatibleClassChangeError.class)
+        .assertFailureWithErrorThatThrowsIf(
+            parameters.isCfRuntime()
+                && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11),
+            AbstractMethodError.class)
+        .assertFailureWithErrorThatThrowsIf(
+            parameters.isDexRuntime(), IncompatibleClassChangeError.class);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, ProgramI.class, ProgramJ.class)
+        .addProgramClassFileData(
+            transformer(ProgramClass.class)
+                .setImplements(LibraryInterface.class, ProgramI.class, ProgramJ.class)
+                .transform())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryClasses(LibraryInterface.class, LibraryCaller.class)
+        .addOptionsModification(
+            options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addDontObfuscate()
+        .compile()
+        .addBootClasspathClasses(LibraryInterface.class, LibraryCaller.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrowsIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(), AbstractMethodError.class)
+        .assertFailureWithErrorThatThrowsIf(
+            !parameters.canUseDefaultAndStaticInterfaceMethods(),
+            IncompatibleClassChangeError.class);
+  }
+
+  private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
+    if (mode.isInitialTreeShaking()) {
+      if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+        verifyMethodFooOnHolderIsSetAsLibraryOverride(appInfo, ProgramI.class);
+        verifyMethodFooOnHolderIsSetAsLibraryOverride(appInfo, ProgramJ.class);
+      } else {
+        verifyMethodFooOnHolderIsSetAsLibraryOverride(appInfo, ProgramClass.class);
+      }
+    }
+  }
+
+  private void verifyMethodFooOnHolderIsSetAsLibraryOverride(
+      AppInfoWithLiveness appInfo, Class<?> programClass) {
+    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+    DexProgramClass clazz =
+        appInfo.definitionFor(dexItemFactory.createType(descriptor(programClass))).asProgramClass();
+    DexEncodedMethod method =
+        clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
+    assertTrue(method.isLibraryMethodOverride().isTrue());
+  }
+
+  public interface LibraryInterface {
+
+    void foo();
+  }
+
+  public static class LibraryCaller {
+
+    public static void callFoo(LibraryInterface iface) {
+      iface.foo();
+    }
+  }
+
+  public interface ProgramI extends LibraryInterface {
+
+    @Override
+    default void foo() {
+      System.out.println("ProgramI::foo");
+    }
+  }
+
+  public interface ProgramJ extends LibraryInterface {
+
+    @Override
+    default void foo() {
+      System.out.println("ProgramJ::foo");
+    }
+  }
+
+  public static class ProgramClass implements LibraryInterface, ProgramI /* ,ProgramJ */ {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      LibraryCaller.callFoo(new ProgramClass());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodTest.java
index 939b14a..046c2e5 100644
--- a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.shaking.librarymethodoverride;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
@@ -58,13 +60,26 @@
 
   private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
     DexItemFactory dexItemFactory = appInfo.dexItemFactory();
-    DexProgramClass clazz =
+    DexProgramClass programI =
         appInfo
             .definitionFor(dexItemFactory.createType(descriptor(ProgramI.class)))
             .asProgramClass();
-    DexEncodedMethod method =
-        clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
-    assertTrue(method.isLibraryMethodOverride().isTrue());
+    DexEncodedMethod programIFoo =
+        programI.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
+    assertEquals(
+        parameters.canUseDefaultAndStaticInterfaceMethods(),
+        programIFoo.isLibraryMethodOverride().isTrue());
+    DexProgramClass programClass =
+        appInfo
+            .definitionFor(dexItemFactory.createType(descriptor(ProgramClass.class)))
+            .asProgramClass();
+    DexEncodedMethod programClassFoo =
+        programClass.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      assertNull(programClassFoo);
+    } else {
+      assertTrue(programClassFoo.isLibraryMethodOverride().isTrue());
+    }
   }
 
   public interface LibraryI {
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfClassMethodWithInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfClassMethodWithInterfaceTest.java
index 247b780..4524dd7 100644
--- a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfClassMethodWithInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfClassMethodWithInterfaceTest.java
@@ -66,8 +66,7 @@
             .asProgramClass();
     DexEncodedMethod method =
         clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
-    // TODO(b/259531498): We should not mark the interface method as overriding.
-    assertTrue(method.isLibraryMethodOverride().isTrue());
+    assertTrue(method.isLibraryMethodOverride().isFalse());
   }
 
   public abstract static class LibraryClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfSuperClassNotInHierarchyTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfSuperClassNotInHierarchyTest.java
new file mode 100644
index 0000000..c9c8bf8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfSuperClassNotInHierarchyTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.librarymethodoverride;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.utils.OptionalBool;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideOfSuperClassNotInHierarchyTest extends TestBase {
+
+  private final String[] EXPECTED = new String[] {"SecondProgramClass::foo"};
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(
+            FirstProgramClass.class, SecondProgramClass.class, ThirdProgramClass.class, Main.class)
+        .addLibraryClasses(LibraryClass.class, LibraryInterface.class)
+        .addRunClasspathFiles(
+            buildOnDexRuntime(parameters, LibraryClass.class, LibraryInterface.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            FirstProgramClass.class, SecondProgramClass.class, ThirdProgramClass.class, Main.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryClasses(LibraryClass.class, LibraryInterface.class)
+        .addOptionsModification(
+            options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .compile()
+        .addBootClasspathClasses(LibraryClass.class, LibraryInterface.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
+    if (!mode.isInitialTreeShaking()) {
+      return;
+    }
+    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+    DexProgramClass clazz =
+        appInfo
+            .definitionFor(dexItemFactory.createType(descriptor(FirstProgramClass.class)))
+            .asProgramClass();
+    DexEncodedMethod method =
+        clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
+    assertEquals(OptionalBool.FALSE, method.isLibraryMethodOverride());
+    clazz =
+        appInfo
+            .definitionFor(dexItemFactory.createType(descriptor(SecondProgramClass.class)))
+            .asProgramClass();
+    method = clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
+    assertEquals(OptionalBool.TRUE, method.isLibraryMethodOverride());
+  }
+
+  public interface LibraryInterface {
+
+    void foo();
+  }
+
+  public static class LibraryClass {
+
+    public static void callFoo(LibraryInterface i) {
+      i.foo();
+    }
+  }
+
+  public static class FirstProgramClass {
+
+    public void foo() {
+      System.out.println("FirstProgramClass::foo");
+    }
+  }
+
+  public static class SecondProgramClass extends FirstProgramClass {
+
+    @Override
+    public void foo() {
+      System.out.println("SecondProgramClass::foo");
+    }
+  }
+
+  public static class ThirdProgramClass extends SecondProgramClass implements LibraryInterface {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      LibraryClass.callFoo(new ThirdProgramClass());
+    }
+  }
+}