[ApiModel] Outline checkcast instructions

Bug: b/258270051
Change-Id: Ic260dc2fa65418d01957c6b5a5767fbfd049fa01
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 6c30ad3..bec1872 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -144,4 +144,14 @@
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     return frame.popInitialized(appView, config, dexItemFactory.objectType).push(config, type);
   }
+
+  @Override
+  public boolean isCheckCast() {
+    return true;
+  }
+
+  @Override
+  public CfCheckCast asCheckCast() {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index b137991..f21fc0c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -412,4 +412,12 @@
 
   public abstract CfFrameState evaluate(
       CfFrameState frame, AppView<?> appView, CfAnalysisConfig config);
+
+  public boolean isCheckCast() {
+    return false;
+  }
+
+  public CfCheckCast asCheckCast() {
+    return null;
+  }
 }
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 3050bdc..dfbfc0e 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
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
@@ -31,6 +33,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.synthetic.CheckCastSourceCode;
 import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
@@ -51,9 +54,12 @@
   private final AppView<?> appView;
   private final AndroidApiLevelCompute apiLevelCompute;
 
+  private final DexTypeList objectParams;
+
   public ApiInvokeOutlinerDesugaring(AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
     this.appView = appView;
     this.apiLevelCompute = apiLevelCompute;
+    this.objectParams = DexTypeList.create(new DexType[] {appView.dexItemFactory().objectType});
   }
 
   @Override
@@ -91,9 +97,6 @@
 
   private ComputedApiLevel getComputedApiLevelInstructionOnHolderWithMinApi(
       CfInstruction instruction) {
-    if (!instruction.isInvoke() && !instruction.isFieldInstruction()) {
-      return appView.computedMinApiLevel();
-    }
     DexReference reference;
     if (instruction.isInvoke()) {
       CfInvoke cfInvoke = instruction.asInvoke();
@@ -101,8 +104,12 @@
         return appView.computedMinApiLevel();
       }
       reference = cfInvoke.getMethod();
-    } else {
+    } else if (instruction.isFieldInstruction()) {
       reference = instruction.asFieldInstruction().getField();
+    } else if (instruction.isCheckCast()) {
+      reference = instruction.asCheckCast().getType();
+    } else {
+      return appView.computedMinApiLevel();
     }
     if (!reference.getContextType().isClassType()) {
       return appView.computedMinApiLevel();
@@ -119,7 +126,7 @@
       return appView.computedMinApiLevel();
     }
     // Check for protected or package private access flags before outlining.
-    if (holder.isInterface()) {
+    if (holder.isInterface() || instruction.isCheckCast()) {
       return referenceApiLevel;
     } else {
       DexEncodedMember<?, ?> definition =
@@ -167,7 +174,7 @@
       DexItemFactory factory,
       ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
       ProgramMethod context) {
-    assert instruction.isInvoke() || instruction.isFieldInstruction();
+    assert instruction.isInvoke() || instruction.isFieldInstruction() || instruction.isCheckCast();
     ProgramMethod outlinedMethod =
         ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
     eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
@@ -199,6 +206,8 @@
                   .setApiLevelForCode(apiLevel);
               if (instruction.isInvoke()) {
                 setCodeForInvoke(syntheticMethodBuilder, instruction.asInvoke(), factory);
+              } else if (instruction.isCheckCast()) {
+                setCodeForCheckCast(syntheticMethodBuilder, instruction.asCheckCast(), factory);
               } else {
                 assert instruction.isCfInstruction();
                 setCodeForFieldInstruction(
@@ -273,6 +282,18 @@
                     .build());
   }
 
+  private void setCodeForCheckCast(
+      SyntheticMethodBuilder methodBuilder, CfCheckCast instruction, DexItemFactory factory) {
+    DexClass target = appView.definitionFor(instruction.getType());
+    assert target != null;
+    methodBuilder
+        .setProto(factory.createProto(target.getType(), objectParams))
+        .setCode(
+            m ->
+                CheckCastSourceCode.create(appView, m.getHolderType(), target.getType())
+                    .generateCfCode());
+  }
+
   private boolean verifyLibraryHolderAndInvoke(
       DexClass libraryHolder, DexMethod apiMethod, boolean isVirtualInvoke) {
     DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/CheckCastSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/CheckCastSourceCode.java
new file mode 100644
index 0000000..32fe7c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/CheckCastSourceCode.java
@@ -0,0 +1,41 @@
+// 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.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code representing a simple call to CheckCast.
+public final class CheckCastSourceCode extends SyntheticCfCodeProvider {
+
+  private final DexType checkCastType;
+
+  private CheckCastSourceCode(AppView<?> appView, DexType holder, DexType checkCastType) {
+    super(appView, holder);
+    this.checkCastType = checkCastType;
+  }
+
+  public static CheckCastSourceCode create(
+      AppView<?> appView, DexType holder, DexType checkCastType) {
+    return new CheckCastSourceCode(appView, holder, checkCastType);
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfCheckCast(checkCastType));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
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 fcd894d..e9857a0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -118,12 +118,12 @@
 
   @Override
   public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
-    // CheckCast are not causing soft verification issues
+    setMaxApiReferenceLevel(type);
   }
 
   @Override
   public void registerSafeCheckCast(DexType type) {
-    // CheckCast are not causing soft verification issues
+    setMaxApiReferenceLevel(type);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
index f946a84..75e1da7 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
@@ -51,14 +51,20 @@
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
-                  if (Reference.methodFromMethod(readApiField).equals(method)
-                      || Reference.methodFromMethod(setApiField).equals(method)) {
+                  if (Reference.methodFromMethod(readApiField).equals(method)) {
                     if (parameters.isCfRuntime()) {
                       assertEquals(AndroidApiLevel.B, apiLevel);
                     } else {
                       assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
                     }
                   }
+                  if (Reference.methodFromMethod(setApiField).equals(method)) {
+                    if (parameters.isCfRuntime()) {
+                      assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+                    } else {
+                      assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+                    }
+                  }
                 }))
         .compile()
         .addRunClasspathClasses(Api.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
index 73132ae..2a0d763 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -38,7 +38,6 @@
   @Test
   public void testR8() throws Exception {
     Method apiCallerApiLevel22 = ApiCaller.class.getDeclaredMethod("apiLevel22");
-    Method apiCallerCallerApiLevel22 = ApiCallerCaller.class.getDeclaredMethod("apiLevel22");
     testForR8(parameters.getBackend())
         .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, OtherCaller.class, Main.class)
         .addLibraryClasses(ApiType.class)
@@ -55,8 +54,14 @@
         .assertSuccessWithOutputLines(ApiType.class.getName())
         .inspect(
             inspector -> {
-              verifyThat(inspector, parameters, apiCallerApiLevel22)
-                  .inlinedInto(apiCallerCallerApiLevel22);
+              assertThat(
+                  inspector.method(apiCallerApiLevel22),
+                  notIf(
+                      isPresent(),
+                      parameters.isDexRuntime()
+                          && parameters
+                              .getApiLevel()
+                              .isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)));
               assertThat(inspector.clazz(OtherCaller.class), not(isPresent()));
             });
   }
@@ -68,7 +73,7 @@
 
     public static ApiType apiLevel22() throws Exception {
       // The reflective call here is to ensure that the setting of A's api level is not based on
-      // a method reference to `Api` and only because of the type reference in the field `api`.
+      // a method reference to `Api` and only because of the type reference in the checkcast.
       Class<?> reflectiveCall =
           Class.forName(
               "com.android.tools.r8.apimodel.ApiModelInlineMethodWithApiTypeTest_ApiType"
@@ -82,7 +87,7 @@
 
     public static void apiLevel22() throws Exception {
       // This is referencing the proto of ApiCaller.foo and thus have a reference to ApiType. It is
-      // therefore OK to inline ApiCaller.foo() into ApiCallerCaller.bar().
+      // therefore OK to inline ApiCaller.apiLevel22() into ApiCallerCaller.apiLevel22().
       System.out.println(ApiCaller.apiLevel22().getClass().getName());
     }
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
index 53b4942..5df420d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
@@ -51,14 +51,20 @@
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
-                  if (Reference.methodFromMethod(readApi).equals(method)
-                      || Reference.methodFromMethod(setApi).equals(method)) {
+                  if (Reference.methodFromMethod(readApi).equals(method)) {
                     if (parameters.isCfRuntime()) {
                       assertEquals(AndroidApiLevel.B, apiLevel);
                     } else {
                       assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
                     }
                   }
+                  if (Reference.methodFromMethod(setApi).equals(method)) {
+                    if (parameters.isCfRuntime()) {
+                      assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+                    } else {
+                      assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+                    }
+                  }
                 }))
         .compile()
         .addRunClasspathClasses(Api.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
index 22a7321..dec0a00 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.apimodel;
 
+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;
 
@@ -48,6 +49,7 @@
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
         .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         // We are testing that we do not inline/merge higher api-levels
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
index dfb1a23..663a649 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
@@ -85,7 +85,7 @@
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
-        .inspect(inspector -> inspect(inspector, false))
+        .inspect(this::inspect)
         .applyIf(
             addToBootClasspath(),
             b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
@@ -101,7 +101,7 @@
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
         .compile()
-        .inspect(inspector -> inspect(inspector, false))
+        .inspect(this::inspect)
         .applyIf(
             addToBootClasspath(),
             b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
@@ -116,7 +116,7 @@
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
         .compile()
-        .inspect(inspector -> inspect(inspector, true))
+        .inspect(this::inspect)
         .applyIf(
             addToBootClasspath(),
             b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
@@ -125,10 +125,9 @@
         .apply(this::checkOutput);
   }
 
-  private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
+  private void inspect(CodeInspector inspector) throws Exception {
     verifyThat(inspector, parameters, LibraryClass.class)
-        // TODO(b/b/258270051): We should outline the check cast.
-        .hasNotCheckCastOutlinedFrom(Main.class.getMethod("main", String[].class));
+        .hasCheckCastOutlinedFromUntil(Main.class.getMethod("main", String[].class), classApiLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
index 79df3a4..8d59924 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
@@ -6,7 +6,8 @@
 
 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.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -34,8 +35,6 @@
   @Test
   public void testR8() throws Exception {
     Method apiMethod = LibraryClass.class.getDeclaredMethod("apiMethod");
-    Method apiCaller = ApiHelper.class.getDeclaredMethod("apiCaller", LibraryClass.class);
-    Method apiCallerCaller = Main.class.getDeclaredMethod("typeReference", Object.class);
     boolean libraryClassOnBoot =
         parameters.isDexRuntime()
             && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.M);
@@ -53,10 +52,7 @@
         .enableInliningAnnotations()
         .addAndroidBuildVersion()
         .compile()
-        .inspect(
-            inspector ->
-                verifyThat(inspector, parameters, apiCaller)
-                    .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.M))
+        .inspect(inspector -> assertThat(inspector.clazz(ApiHelper.class), isAbsent()))
         .addRunClasspathClasses(LibraryClass.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLinesIf(