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");
+ }
+}