Add tests for rewriting subtypes into FilledNewArray

Bug: b/283715197
Change-Id: I24d847474f604ce7f0b95b3592a2c57faf1b19fe
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 7e64f85..904d325 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -112,6 +112,10 @@
     return false;
   }
 
+  public boolean canUseFilledNewArrayOnNonStringObjects() {
+    return isDexRuntime() && getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+  }
+
   public boolean runtimeWithClassValue() {
     assert isCfRuntime() || isDexRuntime();
     return isCfRuntime() || getDexRuntimeVersion().isNewerThanOrEqual(DexVm.Version.V14_0_0);
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataRemoveCheckCastTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataRemoveCheckCastTest.java
new file mode 100644
index 0000000..1f4e6fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataRemoveCheckCastTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2023, 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.rewrite.arrays;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+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.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Optional;
+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;
+
+/**
+ * This is a reproduction of b/283715197. It actually does not reproduce on our headless VMs, so we
+ * cannot assert an error - only the existences of the instruction that causes verification error.
+ */
+@RunWith(Parameterized.class)
+public class FilledArrayDataRemoveCheckCastTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters)
+        .addKeepClassAndMembersRules(Main.class)
+        .addKeepClassAndMembersRules(Base.class)
+        .addKeepClassRulesWithAllowObfuscation(Sub1.class, Sub2.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello", "World", "Hello", "World")
+        .inspect(
+            inspector -> {
+              ClassSubject mainClass = inspector.clazz(Main.class);
+              assertThat(mainClass, isPresent());
+              MethodSubject iterateSubClasses =
+                  mainClass.uniqueMethodWithOriginalName("iterateSubClasses");
+              assertThat(iterateSubClasses, isPresent());
+              Optional<InstructionSubject> filledNewArrayInIterateSubClasses =
+                  iterateSubClasses
+                      .streamInstructions()
+                      .filter(InstructionSubject::isFilledNewArray)
+                      .findFirst();
+              MethodSubject iterateBaseClasses =
+                  mainClass.uniqueMethodWithOriginalName("iterateBaseClasses");
+              assertThat(iterateBaseClasses, isPresent());
+              Optional<InstructionSubject> filledNewArrayInIterateBaseClasses =
+                  iterateBaseClasses
+                      .streamInstructions()
+                      .filter(InstructionSubject::isFilledNewArray)
+                      .findFirst();
+              assertEquals(
+                  parameters.canUseFilledNewArrayOnNonStringObjects(),
+                  filledNewArrayInIterateBaseClasses.isPresent());
+              // TODO(b/283715197): It should not be present.
+              assertEquals(
+                  parameters.canUseFilledNewArrayOnNonStringObjects(),
+                  filledNewArrayInIterateSubClasses.isPresent());
+            });
+  }
+
+  public abstract static class Base {
+
+    public abstract String toString();
+  }
+
+  public static class Sub1 extends Base {
+
+    @Override
+    public String toString() {
+      return "Hello";
+    }
+  }
+
+  public static class Sub2 extends Base {
+
+    @Override
+    public String toString() {
+      return "World";
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      iterateBaseClasses(new Sub1(), new Sub2());
+      iterateSubClasses();
+    }
+
+    public static void iterateBaseClasses(Base b1, Base b2) {
+      Base[] arr = new Base[] {b1, b2};
+      iterate(arr);
+    }
+
+    public static void iterateSubClasses() {
+      Base[] arr = new Base[] {new Sub1(), new Sub2()};
+      iterate(arr);
+    }
+
+    public static void iterate(Object[] arr) {
+      for (Object b : arr) {
+        System.out.println(b.toString());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 23684e1..45d6202 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -380,6 +380,11 @@
   }
 
   @Override
+  public boolean isFilledNewArray() {
+    return false;
+  }
+
+  @Override
   public boolean isNewArray() {
     return instruction instanceof CfNewArray;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 16a41b1..38421a2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -49,6 +49,7 @@
 import com.android.tools.r8.dex.code.DexDivIntLit8;
 import com.android.tools.r8.dex.code.DexDivLong;
 import com.android.tools.r8.dex.code.DexDivLong2Addr;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.dex.code.DexGoto;
 import com.android.tools.r8.dex.code.DexIfEq;
 import com.android.tools.r8.dex.code.DexIfEqz;
@@ -624,6 +625,11 @@
   }
 
   @Override
+  public boolean isFilledNewArray() {
+    return instruction instanceof DexFilledNewArray;
+  }
+
+  @Override
   public int size() {
     return instruction.getSize();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 7fd166e..6d1bd94 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -158,6 +158,8 @@
 
   boolean isMonitorExit();
 
+  boolean isFilledNewArray();
+
   int size();
 
   InstructionOffsetSubject getOffset(MethodSubject methodSubject);