Version 1.3.25
Merge: Add test for array-return-type collision
CL: https://r8-review.googlesource.com/c/r8/+/29345
Merge: Handle array type collisions in vertical class merger
CL: https://r8-review.googlesource.com/c/r8/+/29342
Partial merge: Static, horizontal class merging
CL: https://r8-review.googlesource.com/c/r8/+/28780
The commit "Static, horizontal class merging" contains a fix for the vertical class merger. This CL only merges that fix from https://r8-review.googlesource.com/c/r8/+/28780 (in particular, this merge does not include the horizontal class merger).
Bug: 117399364
Change-Id: If382ec986fdae0e44b2d37b6d509385ddd58bc06
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 1c1e02a..5ee47e8 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.3.24";
+ public static final String LABEL = "1.3.25";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 5e03071..ce5a358 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1495,12 +1495,13 @@
result = 0;
int bitsUsed = 0;
int accumulator = 0;
- for (DexType aType : proto.parameters.values) {
+ for (DexType parameterType : proto.parameters.values) {
+ DexType parameterBaseType = parameterType.toBaseType(appInfo.dexItemFactory);
// Substitute the type with the already merged class to estimate what it will look like.
- aType = mergedClasses.getOrDefault(aType, aType);
+ DexType mappedType = mergedClasses.getOrDefault(parameterBaseType, parameterBaseType);
accumulator <<= 1;
bitsUsed++;
- if (aType == type) {
+ if (mappedType == type) {
accumulator |= 1;
}
// Handle overflow on 31 bit boundary.
@@ -1511,9 +1512,10 @@
}
}
// We also take the return type into account for potential conflicts.
- DexType returnType = mergedClasses.getOrDefault(proto.returnType, proto.returnType);
+ DexType returnBaseType = proto.returnType.toBaseType(appInfo.dexItemFactory);
+ DexType mappedReturnType = mergedClasses.getOrDefault(returnBaseType, returnBaseType);
accumulator <<= 1;
- if (returnType == type) {
+ if (mappedReturnType == type) {
accumulator |= 1;
}
result |= accumulator;
@@ -1628,7 +1630,8 @@
private boolean checkFieldReference(DexField field) {
if (!foundIllegalAccess) {
- if (field.clazz.isSamePackage(source.type)) {
+ DexType baseType = field.clazz.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isClassType() && baseType.isSamePackage(source.type)) {
checkTypeReference(field.clazz);
checkTypeReference(field.type);
@@ -1643,7 +1646,8 @@
private boolean checkMethodReference(DexMethod method) {
if (!foundIllegalAccess) {
- if (method.holder.isSamePackage(source.type)) {
+ DexType baseType = method.holder.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isClassType() && baseType.isSamePackage(source.type)) {
checkTypeReference(method.holder);
checkTypeReference(method.proto.returnType);
for (DexType type : method.proto.parameters.values) {
@@ -1660,8 +1664,9 @@
private boolean checkTypeReference(DexType type) {
if (!foundIllegalAccess) {
- if (type.isClassType() && type.isSamePackage(source.type)) {
- DexClass clazz = appInfo.definitionFor(type);
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isClassType() && baseType.isSamePackage(source.type)) {
+ DexClass clazz = appInfo.definitionFor(baseType);
if (clazz == null || !clazz.accessFlags.isPublic()) {
foundIllegalAccess = true;
}
diff --git a/src/test/examples/classmerging/ArrayTypeCollisionTest.java b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
new file mode 100644
index 0000000..9e4c9fd
--- /dev/null
+++ b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2018, 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 classmerging;
+
+public class ArrayTypeCollisionTest {
+
+ public static void main(String[] args) {
+ method(new A[] {});
+ method(new B[] {});
+ }
+
+ private static void method(A[] obj) {
+ System.out.println("In method(A[])");
+ }
+
+ private static void method(B[] obj) {
+ System.out.println("In method(B[])");
+ }
+
+ // A cannot be merged into B because that would lead to a collision.
+ public static class A {}
+
+ public static class B extends A {}
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 97be613..e19eb97 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
-keep public class classmerging.Test {
public static void main(...);
}
+-keep public class classmerging.ArrayTypeCollisionTest {
+ public static void main(...);
+}
-keep public class classmerging.CallGraphCycleTest {
public static void main(...);
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 7a1f831..1ac4d8e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -37,6 +37,7 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -126,6 +127,92 @@
}
}
+ @Test
+ public void testArrayTypeCollision() throws Throwable {
+ String main = "classmerging.ArrayTypeCollisionTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ArrayTypeCollisionTest.class"),
+ CF_DIR.resolve("ArrayTypeCollisionTest$A.class"),
+ CF_DIR.resolve("ArrayTypeCollisionTest$B.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.ArrayTypeCollisionTest",
+ "classmerging.ArrayTypeCollisionTest$A",
+ "classmerging.ArrayTypeCollisionTest$B");
+ runTest(
+ main,
+ programFiles,
+ preservedClassNames::contains,
+ getProguardConfig(
+ EXAMPLE_KEEP,
+ "-neverinline public class classmerging.ArrayTypeCollisionTest {",
+ " static void method(...);",
+ "}"));
+ }
+
+ /**
+ * Tests that two classes A and B are not merged when there are two methods in the same class that
+ * returns an array type and would become conflicting due to the renaming of their return types,
+ * as in the following class.
+ *
+ * <pre>
+ * class ArrayReturnTypeCollisionTest {
+ * public static A[] method() { ... }
+ * public static B[] method() { ... }
+ * }
+ * </pre>
+ */
+ @Test
+ public void testArrayReturnTypeCollision() throws Throwable {
+ String main = "classmerging.ArrayReturnTypeCollisionTest";
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.ArrayReturnTypeCollisionTest", "classmerging.A", "classmerging.B");
+
+ JasminBuilder jasminBuilder = new JasminBuilder();
+
+ ClassBuilder classBuilder = jasminBuilder.addClass(main);
+ classBuilder.addMainMethod(
+ ".limit locals 1",
+ ".limit stack 2",
+ "invokestatic classmerging/ArrayReturnTypeCollisionTest/method()[Lclassmerging/A;",
+ "pop",
+ "invokestatic classmerging/ArrayReturnTypeCollisionTest/method()[Lclassmerging/B;",
+ "pop",
+ "return");
+
+ // Add two methods with the same name that have return types A[] and B[], respectively.
+ classBuilder.addStaticMethod(
+ "method", ImmutableList.of(), "[Lclassmerging/A;",
+ ".limit stack 1", ".limit locals 1", "iconst_0", "anewarray classmerging/A", "areturn");
+ classBuilder.addStaticMethod(
+ "method", ImmutableList.of(), "[Lclassmerging/B;",
+ ".limit stack 1", ".limit locals 1", "iconst_0", "anewarray classmerging/B", "areturn");
+
+ // Class A is empty so that it can easily be merged into B.
+ classBuilder = jasminBuilder.addClass("classmerging.A");
+ classBuilder.addDefaultConstructor();
+
+ // Class B inherits from A and is also empty.
+ classBuilder = jasminBuilder.addClass("classmerging.B", "classmerging.A");
+ classBuilder.addDefaultConstructor();
+
+ // Run test.
+ runTestOnInput(
+ main,
+ jasminBuilder.build(),
+ preservedClassNames::contains,
+ StringUtils.joinLines(
+ "-keep class " + main + " {",
+ " public static void main(...);",
+ "}",
+ "-neverinline class " + main + " {",
+ " static void method(...);",
+ "}"));
+ }
+
// This test has a cycle in the call graph consisting of the methods A.<init> and B.<init>.
// When nondeterministicCycleElimination is enabled, we shuffle the nodes in the call graph
// before the cycle elimination. Therefore, it is nondeterministic if the cycle detector will