Merge "Guard check-cast elimination when in-value is null constant"
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 7758d83..fbdb9f3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -2112,6 +2112,16 @@
       return false;
     }
 
+    // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
+    // elimination may lead to verification errors. See b/123269162.
+    if (options.canHaveArtCheckCastVerifierBug()) {
+      if (inValue.getTypeLattice().isNullType()
+          && castType.isArrayType()
+          && castType.toBaseType(dexItemFactory).isFloatType()) {
+        return false;
+      }
+    }
+
     // We might see chains of casts on subtypes. It suffices to cast to the lowest subtype,
     // as that will fail if a cast on a supertype would have failed.
     Predicate<Instruction> isCheckcastToSubtype =
@@ -2156,6 +2166,9 @@
 
   private boolean isTypeInaccessibleInCurrentContext(DexType type, DexEncodedMethod context) {
     DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    if (baseType.isPrimitiveType()) {
+      return false;
+    }
     DexClass clazz = definitionFor(baseType);
     if (clazz == null) {
       // Conservatively say yes.
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 3015132..3997982 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -898,4 +898,12 @@
   public boolean canHaveExceptionTypeBug() {
     return minApiLevel < AndroidApiLevel.Q.getLevel();
   }
+
+  // Art 4.0.4 fails with a verification error when a null-literal is being passed directly to an
+  // aget instruction. We therefore need to be careful when performing trivial check-cast
+  // elimination of check-cast instructions where the value being cast is the constant null.
+  // See b/123269162.
+  public boolean canHaveArtCheckCastVerifierBug() {
+    return minApiLevel < AndroidApiLevel.J.getLevel();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
new file mode 100644
index 0000000..8cf8b26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
@@ -0,0 +1,188 @@
+// Copyright (c) 2019, 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.optimize.checkcast;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+/** Regression test for b/123269162. */
+public class TrivialArrayCheckCastTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines(
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    InternalOptions options = new InternalOptions();
+    options.minApiLevel = AndroidApiLevel.I_MR1.getLevel();
+    assert options.canHaveArtCheckCastVerifierBug();
+
+    testForR8(Backend.DEX)
+        .addInnerClasses(TrivialArrayCheckCastTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testBooleanArray();
+      testByteArray();
+      testCharArray();
+      testDoubleArray();
+      testFloatArray();
+      testFloatArrayNested();
+      testIntArray();
+      testLongArray();
+      testObjectArray();
+      testShortArray();
+    }
+
+    @NeverInline
+    private static void testBooleanArray() {
+      boolean[] array = (boolean[]) null;
+      try {
+        boolean value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testByteArray() {
+      byte[] array = (byte[]) null;
+      try {
+        byte value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testCharArray() {
+      char[] array = (char[]) null;
+      try {
+        char value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testDoubleArray() {
+      double[] array = (double[]) null;
+      try {
+        double value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testFloatArray() {
+      float[] array = (float[]) null;
+      try {
+        float value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testFloatArrayNested() {
+      float[][] nestedArray = (float[][]) null;
+      try {
+        float[] array = nestedArray[42];
+        float value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testIntArray() {
+      int[] array = (int[]) null;
+      try {
+        int value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testLongArray() {
+      long[] array = (long[]) null;
+      try {
+        long value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testObjectArray() {
+      Object[] array = (Object[]) null;
+      try {
+        Object value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testShortArray() {
+      short[] array = (short[]) null;
+      try {
+        short value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+  }
+}