Version 1.4.68

Cherry-pick: Rewrite all array methods in lense code rewriter
CL: https://r8-review.googlesource.com/c/r8/+/35400

Bug: 124177369
Change-Id: Ic486c40c4764da8a7098b0399e8c7c3860bf6da9
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b6d1984..60da1a0 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.67";
+  public static final String LABEL = "1.4.68";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index e415b0f..c62df9d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -120,7 +120,8 @@
   // For mapping invoke virtual instruction to target methods.
   public Set<DexEncodedMethod> lookupVirtualTargets(DexMethod method) {
     if (method.holder.isArrayType()) {
-      assert method.name == dexItemFactory.cloneMethodName;
+      // For javac output this will only be clone(), but in general the methods from Object can
+      // be invoked with an array type holder.
       return null;
     }
     DexClass root = definitionFor(method.holder);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index d634827..01c1556 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -151,25 +151,24 @@
           DexMethod invokedMethod = invoke.getInvokedMethod();
           DexType invokedHolder = invokedMethod.getHolder();
           if (invokedHolder.isArrayType()) {
-            if (invokedMethod.name == appInfo.dexItemFactory.cloneMethodName) {
-              DexType baseType = invokedHolder.toBaseType(appInfo.dexItemFactory);
-              DexType mappedBaseType = graphLense.lookupType(baseType);
-              if (baseType != mappedBaseType) {
-                DexType mappedHolder =
-                    invokedHolder.replaceBaseType(mappedBaseType, appInfo.dexItemFactory);
-                // The clone proto is ()Ljava/lang/Object;, so just reuse it.
-                DexMethod actualTarget =
-                    appInfo.dexItemFactory.createMethod(
-                        mappedHolder, invokedMethod.proto, appInfo.dexItemFactory.cloneMethodName);
-                Invoke newInvoke =
-                    Invoke.create(
-                        VIRTUAL,
-                        actualTarget,
-                        null,
-                        makeOutValue(invoke, code, newSSAValues),
-                        invoke.inValues());
-                iterator.replaceCurrentInstruction(newInvoke);
-              }
+            DexType baseType = invokedHolder.toBaseType(appInfo.dexItemFactory);
+            DexType mappedBaseType = graphLense.lookupType(baseType);
+            if (baseType != mappedBaseType) {
+              DexType mappedHolder =
+                  invokedHolder.replaceBaseType(mappedBaseType, appInfo.dexItemFactory);
+              // Just reuse proto and name, as no methods on array types can be renamed nor
+              // change signature.
+              DexMethod actualTarget =
+                  appInfo.dexItemFactory.createMethod(
+                      mappedHolder, invokedMethod.proto, invokedMethod.name);
+              Invoke newInvoke =
+                  Invoke.create(
+                      VIRTUAL,
+                      actualTarget,
+                      null,
+                      makeOutValue(invoke, code, newSSAValues),
+                      invoke.inValues());
+              iterator.replaceCurrentInstruction(newInvoke);
             }
             continue;
           }
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 c60b129..2412662 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2556,7 +2556,8 @@
       assert method != null;
       assert refinedReceiverType.isSubtypeOf(method.holder, this);
       if (method.holder.isArrayType()) {
-        assert method.name == dexItemFactory.cloneMethodName;
+        // For javac output this will only be clone(), but in general the methods from Object can
+        // be invoked with an array type holder.
         return null;
       }
       DexClass holder = definitionFor(method.holder);
diff --git a/src/test/java/com/android/tools/r8/naming/arraytypes/ArrayTypesTest.java b/src/test/java/com/android/tools/r8/naming/arraytypes/ArrayTypesTest.java
new file mode 100644
index 0000000..0ddb8b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/arraytypes/ArrayTypesTest.java
@@ -0,0 +1,322 @@
+// 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.naming.arraytypes;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class ArrayTypesTest extends TestBase {
+
+  private final Backend backend;
+
+  private static String packageName;
+  private static String arrayBaseTypeDescriptor;
+  private static String arrayTypeDescriptor;
+  private static String generatedTestClassName;
+  private static String expectedOutput;
+
+  public ArrayTypesTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Object[] data() {
+    return Backend.values();
+  }
+
+  @BeforeClass
+  public static void setup() {
+    packageName = ArrayTypesTest.class.getPackage().getName();
+    arrayBaseTypeDescriptor =
+        DescriptorUtils.getDescriptorFromClassBinaryName(
+            DescriptorUtils.getBinaryNameFromJavaType(
+                A.class.getTypeName()));
+    arrayTypeDescriptor = "[" + arrayBaseTypeDescriptor;
+    generatedTestClassName = packageName + "." + "GeneratedTestClass";
+    expectedOutput = StringUtils.lines(
+        "javac code:",
+        "Length: EQ",
+        "Hashcode: EQ",
+        "toString: EQ",
+        "ASM code:",
+        "Clones: NE",
+        "Hashcode: EQ",
+        "Compare with Object: true",
+        "Compare with array type: true",
+        "toString: true",
+        "Got expected exception",
+        "Done");
+  }
+
+  private void runR8Test(boolean enableMinification) throws Exception {
+    testForR8(backend)
+        .minification(enableMinification)
+        .addProgramClasses(Main.class, A.class)
+        .addProgramClassFileData(generateTestClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-keep class " + generatedTestClassName + " { test(...); }")
+        .run(Main.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runR8Test(true);
+  }
+
+  @Test
+  public void testR8NoMinification() throws Exception {
+    runR8Test(false);
+  }
+
+  @Test
+  public void testR8ApplyMapping() throws Exception {
+    // Rename the array type (keep it in the same package for access reasons).
+    Path mappingFile = temp.newFile("mapping.txt").toPath();
+    FileUtils.writeTextFile(
+        mappingFile,
+        StringUtils.lines(
+            A.class.getTypeName() + " -> " + packageName + ".a:"));
+
+    testForR8(backend)
+        .addProgramClasses(Main.class, A.class)
+        .addProgramClassFileData(generateTestClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-applymapping " + mappingFile.toAbsolutePath())
+        .noMinification()
+        .noTreeShaking()
+        .run(Main.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    if (backend == Backend.DEX) {
+      testForD8()
+          .addProgramClasses(Main.class, A.class)
+          .addProgramClassFileData(generateTestClass())
+          .run(Main.class)
+          .writeProcessResult(System.out)
+          .assertSuccessWithOutput(expectedOutput);
+    }
+  }
+
+  public static byte[] generateTestClass() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor mv;
+
+    classWriter.visit(
+        Opcodes.V1_8,
+        Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER | Opcodes.ACC_ENUM,
+        DescriptorUtils.getBinaryNameFromJavaType(generatedTestClassName),
+        null,
+        "java/lang/Object",
+        null);
+
+    {
+      mv = classWriter.visitMethod(
+          Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
+          "test", "(" + arrayTypeDescriptor + ")V",
+          null,
+          null);
+
+      mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitLdcInsn("ASM code:");
+      mv.visitMethodInsn(
+          Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+
+      // Invoke clone using both java.lang.Object and array type an holder.
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      mv.visitMethodInsn(
+          Opcodes.INVOKEVIRTUAL, "java/lang/Object", "clone", "()Ljava/lang/Object;", false);
+      mv.visitVarInsn(Opcodes.ASTORE, 1);
+
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      mv.visitMethodInsn(
+          Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "clone", "()Ljava/lang/Object;", false);
+      mv.visitVarInsn(Opcodes.ASTORE, 2);
+
+      printCompareObjectsIdentical("Clones: ", mv, (mvCopy) -> {
+        assert mv == mvCopy;
+        mv.visitVarInsn(Opcodes.ALOAD, 1);
+        mv.visitVarInsn(Opcodes.ALOAD, 2);
+      });
+
+      printCompareIntergers("Hashcode: ", mv, (mvCopy) -> {
+        assert mv == mvCopy;
+        // Invoke hashCode using both java.lang.Object and array type an holder.
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "hashCode", "()I", false);
+
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "hashCode", "()I", false);
+      });
+
+      printBoolean("Compare with Object: ", mv, (mvCopy) -> {
+        assert mv == mvCopy;
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitMethodInsn(
+            Opcodes.INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false);
+      });
+
+      printBoolean("Compare with array type: ", mv, (mvCopy) -> {
+        assert mv == mvCopy;
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitMethodInsn(
+            Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "equals", "(Ljava/lang/Object;)Z", false);
+      });
+
+      printBoolean("toString: ", mv, (mvCopy) -> {
+        assert mv == mvCopy;
+        // Invoke toString using both java.lang.Object and array type an holder.
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitMethodInsn(
+            Opcodes.INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false);
+
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitMethodInsn(
+            Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "toString", "()Ljava/lang/String;", false);
+
+        // Compare the toString strings.
+        mv.visitMethodInsn(
+            Opcodes.INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false);
+      });
+
+      // Finally invoke a method not present on an array type.
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      mv.visitMethodInsn(
+          Opcodes.INVOKEVIRTUAL, arrayTypeDescriptor, "notPresent", "()Ljava/lang/String;", false);
+
+      mv.visitInsn(Opcodes.RETURN);
+      mv.visitMaxs(-1, -1);
+      mv.visitEnd();
+    }
+
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+  public static void printCompareIntergers(
+      String header, MethodVisitor mv, Consumer<MethodVisitor> consumer) {
+    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+    mv.visitLdcInsn(header);
+    mv.visitMethodInsn(
+        Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
+
+    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+
+    consumer.accept(mv);
+
+    Label neLabel = new Label();
+    Label printLabel = new Label();
+    mv.visitJumpInsn(Opcodes.IF_ICMPNE, neLabel);
+    mv.visitLdcInsn("EQ");
+    mv.visitJumpInsn(Opcodes.GOTO, printLabel);
+    mv.visitLabel(neLabel);
+    mv.visitLdcInsn("NE");
+    mv.visitLabel(printLabel);
+    mv.visitMethodInsn(
+        Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+  }
+
+  public static void printCompareObjectsIdentical(
+      String header, MethodVisitor mv, Consumer<MethodVisitor> consumer) {
+    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+    mv.visitLdcInsn(header);
+    mv.visitMethodInsn(
+        Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
+
+    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+
+    consumer.accept(mv);
+
+    Label neLabel = new Label();
+    Label printLabel = new Label();
+    mv.visitJumpInsn(Opcodes.IF_ACMPNE, neLabel);
+    mv.visitLdcInsn("EQ");
+    mv.visitJumpInsn(Opcodes.GOTO, printLabel);
+    mv.visitLabel(neLabel);
+    mv.visitLdcInsn("NE");
+    mv.visitLabel(printLabel);
+    mv.visitMethodInsn(
+        Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+  }
+
+  public static void printBoolean(
+      String header, MethodVisitor mv, Consumer<MethodVisitor> consumer) {
+    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+    mv.visitLdcInsn(header);
+    mv.visitMethodInsn(
+        Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
+
+    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+
+    consumer.accept(mv);
+
+    Label neLabel = new Label();
+    Label printLabel = new Label();
+    mv.visitJumpInsn(Opcodes.IFNE, neLabel);
+    mv.visitLdcInsn("false");
+    mv.visitJumpInsn(Opcodes.GOTO, printLabel);
+    mv.visitLabel(neLabel);
+    mv.visitLdcInsn("true");
+    mv.visitLabel(printLabel);
+    mv.visitMethodInsn(
+        Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+  }
+}
+
+class A {
+
+}
+
+class Main {
+
+  public static void main(String[] args) throws Exception {
+    A[] array = new A[2];
+    A[] clone = array.clone();
+
+    System.out.println("javac code:");
+
+    if (array.length == clone.length) {
+      System.out.println("Length: EQ");
+    }
+
+    if (array.hashCode() == array.hashCode()) {
+      System.out.println("Hashcode: EQ");
+    }
+
+    if (array.toString().equals(array.toString())) {
+      System.out.println("toString: EQ");
+    }
+
+    Class<?> generatedTestClass =
+        Class.forName(Main.class.getPackage().getName() + "." + "GeneratedTestClass");
+    Method testMethod = generatedTestClass.getDeclaredMethod("test", array.getClass());
+    try {
+      testMethod.invoke(null, new Object[]{array});
+    } catch (java.lang.reflect.InvocationTargetException e) {
+      System.out.println("Got expected exception");
+    }
+    System.out.println("Done");
+  }
+}