Reland "[ApiModel] Outline calls to holders where definition is in library"

This reverts commit ad39dcb28b565277e9600ad0909e8f86cb79e95d.

Bug: b/254510678
Change-Id: I3813f9101e540cfc475edc5513ce674260ae9b4f
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 369e60c..1536cd8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -191,9 +191,9 @@
    * given type is *not* visited. The function indicates if traversal should continue or break. The
    * result of the traversal is BREAK iff the function returned BREAK.
    */
-  public TraversalContinuation<?, ?> traverseSuperTypes(
+  public <B> TraversalContinuation<B, ?> traverseSuperTypes(
       final DexClass clazz,
-      TriFunction<DexType, DexClass, Boolean, TraversalContinuation<?, ?>> fn) {
+      TriFunction<DexType, DexClass, Boolean, TraversalContinuation<B, ?>> fn) {
     // We do an initial zero-allocation pass over the class super chain as it does not require a
     // worklist/seen-set. Only if the traversal is not aborted and there actually are interfaces,
     // do we continue traversal over the interface types. This is assuming that the second pass
@@ -206,7 +206,7 @@
         if (currentClass.superType == null) {
           break;
         }
-        TraversalContinuation<?, ?> stepResult =
+        TraversalContinuation<B, ?> stepResult =
             fn.apply(currentClass.superType, currentClass, false);
         if (stepResult.shouldBreak()) {
           return stepResult;
@@ -226,7 +226,7 @@
       while (currentClass != null) {
         for (DexType iface : currentClass.interfaces.values) {
           if (seen.add(iface)) {
-            TraversalContinuation<?, ?> stepResult = fn.apply(iface, currentClass, true);
+            TraversalContinuation<B, ?> stepResult = fn.apply(iface, currentClass, true);
             if (stepResult.shouldBreak()) {
               return stepResult;
             }
@@ -246,7 +246,7 @@
       if (definition != null) {
         for (DexType iface : definition.interfaces.values) {
           if (seen.add(iface)) {
-            TraversalContinuation<?, ?> stepResult = fn.apply(iface, definition, true);
+            TraversalContinuation<B, ?> stepResult = fn.apply(iface, definition, true);
             if (stepResult.shouldBreak()) {
               return stepResult;
             }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 4c3fc63..be663de 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -40,6 +40,8 @@
 import com.android.tools.r8.ir.synthetic.InstanceOfSourceCode;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -119,23 +121,38 @@
       return appView.computedMinApiLevel();
     }
     DexClass holder = appView.definitionFor(reference.getContextType());
-    if (holder == null || !holder.isLibraryClass()) {
+    if (holder == null) {
       return appView.computedMinApiLevel();
     }
-    ComputedApiLevel referenceApiLevel =
-        apiLevelCompute.computeApiLevelForLibraryReference(reference, ComputedApiLevel.unknown());
+    Pair<DexClass, ComputedApiLevel> classAndApiLevel =
+        reference.isDexType()
+            ? Pair.create(
+                holder,
+                apiLevelCompute.computeApiLevelForLibraryReference(
+                    reference, ComputedApiLevel.unknown()))
+            : AndroidApiLevelUtils.findAndComputeApiLevelForLibraryDefinition(
+                appView, appView.appInfoForDesugaring(), holder, reference.asDexMember());
+    ComputedApiLevel referenceApiLevel = classAndApiLevel.getSecond();
     if (appView.computedMinApiLevel().isGreaterThanOrEqualTo(referenceApiLevel)
         || isApiLevelLessThanOrEqualTo9(referenceApiLevel)
         || referenceApiLevel.isUnknownApiLevel()) {
       return appView.computedMinApiLevel();
     }
+    assert referenceApiLevel.isKnownApiLevel();
+    DexClass firstLibraryClass = classAndApiLevel.getFirst();
+    if (firstLibraryClass == null || !firstLibraryClass.isLibraryClass()) {
+      assert false : "When computed a known api level we should always have a library class";
+      return appView.computedMinApiLevel();
+    }
     // Check for protected or package private access flags before outlining.
-    if (holder.isInterface() || instruction.isCheckCast() || instruction.isInstanceOf()) {
+    if (firstLibraryClass.isInterface()
+        || instruction.isCheckCast()
+        || instruction.isInstanceOf()) {
       return referenceApiLevel;
     } else {
       DexEncodedMember<?, ?> definition =
           simpleLookupInClassHierarchy(
-              holder.asLibraryClass(),
+              firstLibraryClass.asLibraryClass(),
               reference.isDexMethod()
                   ? x -> x.lookupMethod(reference.asDexMethod())
                   : x -> x.lookupField(reference.asDexField()));
diff --git a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
index 662f9f4..d779378 100644
--- a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -7,7 +7,9 @@
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
@@ -15,11 +17,13 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
 import java.util.ListIterator;
 
 public class ComputeApiLevelUseRegistry extends UseRegistry<ProgramMethod> {
 
   protected final AppView<?> appView;
+  private final AppInfoWithClassHierarchy appInfoWithClassHierarchy;
   private final AndroidApiLevelCompute apiLevelCompute;
   private final boolean isEnabled;
   private ComputedApiLevel maxApiReferenceLevel;
@@ -28,6 +32,7 @@
       AppView<?> appView, ProgramMethod context, AndroidApiLevelCompute apiLevelCompute) {
     super(appView, context);
     this.appView = appView;
+    this.appInfoWithClassHierarchy = appView.appInfoForDesugaring();
     this.apiLevelCompute = apiLevelCompute;
     isEnabled = apiLevelCompute.isEnabled();
     maxApiReferenceLevel = appView.computedMinApiLevel();
@@ -148,10 +153,28 @@
 
   private void setMaxApiReferenceLevel(DexReference reference) {
     if (isEnabled) {
-      maxApiReferenceLevel =
-          maxApiReferenceLevel.max(
-              apiLevelCompute.computeApiLevelForLibraryReference(
-                  reference, apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
+      if (reference.isDexType()) {
+        maxApiReferenceLevel =
+            maxApiReferenceLevel.max(
+                apiLevelCompute.computeApiLevelForLibraryReference(
+                    reference, apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
+      } else if (!reference.getContextType().isClassType()) {
+        maxApiReferenceLevel = maxApiReferenceLevel.max(appView.computedMinApiLevel());
+      } else {
+        DexClass holder = appView.definitionFor(reference.getContextType());
+        ComputedApiLevel referenceApiLevel = ComputedApiLevel.unknown();
+        if (holder != null) {
+          referenceApiLevel =
+              AndroidApiLevelUtils.findAndComputeApiLevelForLibraryDefinition(
+                      appView, appInfoWithClassHierarchy, holder, reference.asDexMember())
+                  .getSecond();
+        }
+        maxApiReferenceLevel =
+            maxApiReferenceLevel.max(
+                referenceApiLevel.isUnknownApiLevel()
+                    ? apiLevelCompute.getPlatformApiLevelOrUnknown(appView)
+                    : referenceApiLevel);
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 7369593..0a8b899 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -13,6 +13,7 @@
 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.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -22,6 +23,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.Set;
 
 public class AndroidApiLevelUtils {
 
@@ -191,4 +195,109 @@
     return oldBaseLibraryClass != null
         && isApiSafeForReference(newBaseLibraryClass, oldBaseLibraryClass, appView);
   }
+
+  public static Pair<DexClass, ComputedApiLevel> findAndComputeApiLevelForLibraryDefinition(
+      AppView<?> appView,
+      AppInfoWithClassHierarchy appInfo,
+      DexClass holder,
+      DexMember<?, ?> reference) {
+    AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
+    if (holder.isLibraryClass()) {
+      return Pair.create(
+          holder,
+          apiLevelCompute.computeApiLevelForLibraryReference(
+              reference, ComputedApiLevel.unknown()));
+    }
+    // The API database do not allow for resolving into it (since that is not stable), and it is
+    // therefore designed in a way where all members of classes can be queried on any sub-type with
+    // the api level for where it is reachable. It is therefore sufficient for us, to figure out if
+    // an instruction is a library call, to either find a program definition or to find the library
+    // frontier.
+    // Scan through the type hierarchy to find the first library class or program definition.
+    DexClass firstClassWithReferenceOrLibraryClass =
+        firstLibraryClassOrProgramClassWithDefinition(appInfo, holder, reference);
+    if (firstClassWithReferenceOrLibraryClass == null) {
+      return Pair.create(null, ComputedApiLevel.unknown());
+    }
+    if (!firstClassWithReferenceOrLibraryClass.isLibraryClass()) {
+      return Pair.create(firstClassWithReferenceOrLibraryClass, appView.computedMinApiLevel());
+    }
+    ComputedApiLevel apiLevel =
+        apiLevelCompute.computeApiLevelForLibraryReference(
+            reference.withHolder(
+                firstClassWithReferenceOrLibraryClass.getType(), appView.dexItemFactory()),
+            ComputedApiLevel.unknown());
+    if (apiLevel.isKnownApiLevel()) {
+      return Pair.create(firstClassWithReferenceOrLibraryClass, apiLevel);
+    }
+    // We were unable to find a definition in the class hierarchy, check all interfaces for a
+    // definition or the library interfaces for the first interface definition.
+    Set<DexClass> firstLibraryInterfaces =
+        findAllFirstLibraryInterfacesOrProgramClassWithDefinition(appInfo, holder, reference);
+    if (firstLibraryInterfaces.size() == 1) {
+      DexClass firstClass = firstLibraryInterfaces.iterator().next();
+      if (!firstClass.isLibraryClass()) {
+        return Pair.create(firstClass, appView.computedMinApiLevel());
+      }
+    }
+    DexClass foundClass = null;
+    ComputedApiLevel minApiLevel = ComputedApiLevel.unknown();
+    for (DexClass libraryInterface : firstLibraryInterfaces) {
+      assert libraryInterface.isLibraryClass();
+      ComputedApiLevel libraryIfaceApiLevel =
+          apiLevelCompute.computeApiLevelForLibraryReference(
+              reference.withHolder(
+                  firstClassWithReferenceOrLibraryClass.getType(), appView.dexItemFactory()),
+              ComputedApiLevel.unknown());
+      if (minApiLevel.isGreaterThan(libraryIfaceApiLevel)) {
+        minApiLevel = libraryIfaceApiLevel;
+        foundClass = libraryInterface;
+      }
+    }
+    return Pair.create(foundClass, minApiLevel);
+  }
+
+  private static DexClass firstLibraryClassOrProgramClassWithDefinition(
+      AppInfoWithClassHierarchy appInfo, DexClass originalClass, DexMember<?, ?> reference) {
+    if (originalClass.isLibraryClass()) {
+      return originalClass;
+    }
+    WorkList<DexClass> workList = WorkList.newIdentityWorkList(originalClass);
+    while (workList.hasNext()) {
+      DexClass clazz = workList.next();
+      if (clazz.isLibraryClass()) {
+        return clazz;
+      } else if (clazz.lookupMember(reference) != null) {
+        return clazz;
+      } else if (clazz.getSuperType() != null) {
+        appInfo
+            .contextIndependentDefinitionForWithResolutionResult(clazz.getSuperType())
+            .forEachClassResolutionResult(workList::addIfNotSeen);
+      }
+    }
+    return null;
+  }
+
+  private static Set<DexClass> findAllFirstLibraryInterfacesOrProgramClassWithDefinition(
+      AppInfoWithClassHierarchy appInfo, DexClass originalClass, DexMember<?, ?> reference) {
+    Set<DexClass> interfaces = Sets.newLinkedHashSet();
+    WorkList<DexClass> workList = WorkList.newIdentityWorkList(originalClass);
+    while (workList.hasNext()) {
+      DexClass clazz = workList.next();
+      if (clazz.isLibraryClass()) {
+        if (clazz.isInterface()) {
+          interfaces.add(clazz);
+        }
+      } else if (clazz.lookupMember(reference) != null) {
+        return Collections.singleton(clazz);
+      } else {
+        clazz.forEachImmediateSupertype(
+            superType ->
+                appInfo
+                    .contextIndependentDefinitionForWithResolutionResult(superType)
+                    .forEachClassResolutionResult(workList::addIfNotSeen));
+      }
+    }
+    return interfaces;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
index f859e30..3c2923d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.apimodel.ApiModelingTestHelper.ApiModelingMethodVerificationHelper;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -103,7 +104,7 @@
         .apply(this::setupTestBuilder)
         .compile()
         .apply(this::setupRunEnvironment)
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, false))
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, false));
   }
@@ -115,7 +116,7 @@
         .apply(this::setupTestBuilder)
         .compile()
         .apply(this::setupRunEnvironment)
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, false))
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, false));
   }
@@ -127,7 +128,7 @@
         .addKeepMainRule(Main.class)
         .addKeepClassAndMembersRules(ProgramJoiner.class)
         .compile()
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, true))
         .apply(this::setupRunEnvironment)
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, true));
@@ -155,17 +156,25 @@
         result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
   }
 
-  private void inspect(CodeInspector inspector) throws Exception {
-    // TODO(b/254510678): We should outline the call to ProgramJoiner.foo.
-    verifyThat(
+  private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
+    ApiModelingMethodVerificationHelper verifyHelper =
+        verifyThat(
             inspector,
             parameters,
             Reference.method(
-                Reference.classFromClass(ProgramJoiner.class),
+                // TODO(b/254510678): Due to member rebinding, we rebind ProgramJoiner.foo() to
+                //  LibraryClass.foo().
+                Reference.classFromClass(isR8 ? LibraryClass.class : ProgramJoiner.class),
                 "foo",
                 Collections.emptyList(),
-                null))
-        .isNotOutlinedFrom(Main.class.getDeclaredMethod("main", String[].class));
+                null));
+    if (isR8 && parameters.isCfRuntime()) {
+      verifyHelper.isOutlinedFromUntil(
+          Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
+    } else {
+      verifyHelper.isOutlinedFromUntilAlsoForCf(
+          Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
index a0cc571..f6a0a49 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.apimodel.ApiModelingTestHelper.ApiModelingMethodVerificationHelper;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -93,7 +94,7 @@
         .apply(this::setupRunEnvironment)
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, false))
-        .inspect(this::inspect);
+        .inspect(inspector -> inspect(inspector, false));
   }
 
   @Test
@@ -105,7 +106,7 @@
         .apply(this::setupRunEnvironment)
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, false))
-        .inspect(this::inspect);
+        .inspect(inspector -> inspect(inspector, false));
   }
 
   @Test
@@ -115,7 +116,7 @@
         .addKeepMainRule(Main.class)
         .addKeepClassAndMembersRules(ProgramJoiner.class)
         .compile()
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, true))
         .apply(this::setupRunEnvironment)
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkOutput(result, true));
@@ -137,17 +138,25 @@
         result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
   }
 
-  private void inspect(CodeInspector inspector) throws Exception {
-    // TODO(b/254510678): We should outline the call to ProgramClass.foo.
-    verifyThat(
+  private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
+    ApiModelingMethodVerificationHelper verifyHelper =
+        verifyThat(
             inspector,
             parameters,
             Reference.method(
-                Reference.classFromClass(ProgramJoiner.class),
+                // TODO(b/254510678): Due to member rebinding, we rebind ProgramJoiner.foo() to
+                //  LibraryClass.foo().
+                Reference.classFromClass(isR8 ? LibraryClass.class : ProgramJoiner.class),
                 "foo",
                 Collections.emptyList(),
-                null))
-        .isNotOutlinedFrom(Main.class.getDeclaredMethod("main", String[].class));
+                null));
+    if (isR8 && parameters.isCfRuntime()) {
+      verifyHelper.isOutlinedFromUntil(
+          Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
+    } else {
+      verifyHelper.isOutlinedFromUntilAlsoForCf(
+          Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
+    }
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java
index 3e56179..19971c3 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java
@@ -7,9 +7,6 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -23,8 +20,8 @@
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
+import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -133,13 +130,14 @@
     Method otherMethod = Sub.class.getMethod("otherMethod");
     Method libraryMethod = LibraryClass.class.getMethod("foo");
     // TODO(b/254510678): R8 should not member-rebind to a potential non-existing method.
-    if (isR8) {
-      MethodSubject method = inspector.method(otherMethod);
-      assertThat(method, isPresent());
-      assertThat(method, invokesMethod(Reference.methodFromMethod(libraryMethod)));
-    }
-    // TODO(b/254510678): We should outline this up until library api level.
-    verifyThat(inspector, parameters, libraryMethod).isNotOutlinedFrom(otherMethod);
+    verifyThat(
+            inspector,
+            parameters,
+            isR8
+                ? Reference.methodFromMethod(libraryMethod)
+                : Reference.method(
+                    Reference.classFromClass(Sub.class), "foo", Collections.emptyList(), null))
+        .isOutlinedFromUntil(Sub.class.getDeclaredMethod("otherMethod"), libraryApiLevel);
   }
 
   private void checkResultOnBootClassPath(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index e437943..1e45b00 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -447,6 +447,14 @@
       }
     }
 
+    void isOutlinedFromUntilAlsoForCf(Executable method, AndroidApiLevel apiLevel) {
+      if (parameters.getApiLevel().isLessThan(apiLevel)) {
+        isOutlinedFrom(method);
+      } else {
+        isNotOutlinedFrom(method);
+      }
+    }
+
     void isOutlinedFrom(Executable method) {
       // Check that the call is in a synthetic class.
       List<FoundMethodSubject> outlinedMethod =