Fix collisions between fields
Bug: 169394640
Bug: 163311975
Change-Id: Ica076f10a001597c90d569511acefee8de5a689e
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 43c065a..012030b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -169,6 +169,10 @@
return dexItemFactory.createField(holder, type, name);
}
+ public DexField withName(DexString name, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createField(holder, type, name);
+ }
+
public FieldReference asFieldReference() {
return Reference.field(
Reference.classFromDescriptor(holder.toDescriptorString()),
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index cdf97ab..cf30eed 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1799,7 +1799,7 @@
* @param tryString callback to check if the method name is in use.
*/
public <T extends DexMember<?, ?>> T createFreshMember(
- String baseName, DexType holder, Function<DexString, Optional<T>> tryString) {
+ Function<DexString, Optional<T>> tryString, String baseName, DexType holder) {
int index = 0;
while (true) {
DexString name = createString(createMemberString(baseName, holder, index++));
@@ -1811,6 +1811,17 @@
}
/**
+ * Find a fresh method name that is not used by any other method. The method name takes the form
+ * "basename" or "basename$index".
+ *
+ * @param tryString callback to check if the method name is in use.
+ */
+ public <T extends DexMember<?, ?>> T createFreshMember(
+ Function<DexString, Optional<T>> tryString, String baseName) {
+ return createFreshMember(tryString, baseName, null);
+ }
+
+ /**
* Find a fresh method name that is not in the string pool. The name takes the form
* "basename$holdername" or "basename$holdername$index".
*/
@@ -1850,8 +1861,6 @@
Predicate<DexMethod> isFresh) {
DexMethod method =
createFreshMember(
- baseName,
- holder,
name -> {
DexMethod tryMethod = createMethod(target, proto, name);
if (isFresh.test(tryMethod)) {
@@ -1859,7 +1868,9 @@
} else {
return Optional.empty();
}
- });
+ },
+ baseName,
+ holder);
return method;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index e33137c..7f12cc9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -32,6 +32,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@@ -384,10 +385,26 @@
if (fields == null) {
return;
}
+ Set<DexField> existingFields = Sets.newIdentityHashSet();
+
for (int i = 0; i < fields.size(); i++) {
DexEncodedField encodedField = fields.get(i);
DexField field = encodedField.field;
DexField newField = fixupFieldReference(field);
+
+ // Rename the field if it already exists.
+ if (!existingFields.add(newField)) {
+ DexField template = newField;
+ newField =
+ dexItemFactory.createFreshMember(
+ tryName ->
+ Optional.of(template.withName(tryName, dexItemFactory))
+ .filter(tryMethod -> !existingFields.contains(tryMethod)),
+ newField.name.toSourceString());
+ boolean added = existingFields.add(newField);
+ assert added;
+ }
+
if (newField != encodedField.field) {
lensBuilder.mapField(field, newField);
setter.setField(i, encodedField.toTypeSubstitutedField(newField));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
new file mode 100644
index 0000000..d25d516
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+
+public class MergingProducesFieldCollisionTest extends HorizontalClassMergingTestBase {
+ public MergingProducesFieldCollisionTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ byte[] transformedC = transformer(C.class).renameAndRemapField("v$1", "v").transform();
+
+ testForR8(parameters.getBackend())
+ .addKeepMainRule(Main.class)
+ .addProgramClassFileData(transformedC)
+ .addProgramClasses(Parent.class, A.class, B.class, Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccess()
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(Parent.class), isPresent());
+
+ ClassSubject aClassSubject = codeInspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ ClassSubject bClassSubject = codeInspector.clazz(B.class);
+ assertThat(bClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
+
+ ClassSubject cClassSubject = codeInspector.clazz(C.class);
+ assertThat(cClassSubject, isPresent());
+
+ if (enableHorizontalClassMerging) {
+ assertEquals(
+ cClassSubject.allFields().get(0).type(),
+ cClassSubject.allFields().get(1).type());
+ }
+ });
+ }
+
+ @NoVerticalClassMerging
+ public static class Parent {
+ @NeverInline
+ public void foo() {
+ System.out.println("foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class A extends Parent {
+ public A() {
+ System.out.println("b");
+ }
+ }
+
+ @NeverClassInline
+ public static class B extends Parent {
+ public B() {
+ System.out.println("b");
+ }
+
+ @NeverInline
+ public void foo() {
+ System.out.println("foo b");
+ }
+ }
+
+ @NeverClassInline
+ public static class C {
+ A v;
+ B v$1; // This field is renamed to v.
+
+ public C(A a, B b) {
+ v = a;
+ v$1 = b;
+ }
+
+ @NeverInline
+ public void foo() {
+ v.foo();
+ v$1.foo();
+ }
+ }
+
+ public static class Main {
+ public static void main(String[] args) {
+ new C(new A(), new B()).foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 4779b8e..d9f3b1d 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -474,6 +474,11 @@
boolean test(int access, String name, String descriptor, String signature, Object value);
}
+ @FunctionalInterface
+ public interface FieldSignaturePredicate {
+ boolean test(String name, String typeDescriptor);
+ }
+
public ClassFileTransformer removeInnerClasses() {
return addClassTransformer(
new ClassTransformer() {
@@ -522,6 +527,42 @@
});
}
+ public ClassFileTransformer remapField(FieldSignaturePredicate predicate, String newName) {
+ return addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitFieldInsn(
+ final int opcode, final String owner, final String name, final String descriptor) {
+ if (predicate.test(name, descriptor)) {
+ super.visitFieldInsn(opcode, owner, newName, descriptor);
+ } else {
+ super.visitFieldInsn(opcode, owner, name, descriptor);
+ }
+ }
+ });
+ }
+
+ public ClassFileTransformer renameField(FieldSignaturePredicate predicate, String newName) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public FieldVisitor visitField(
+ int access, String name, String descriptor, String signature, Object value) {
+ if (predicate.test(name, descriptor)) {
+ return super.visitField(access, newName, descriptor, signature, value);
+ } else {
+ return super.visitField(access, name, descriptor, signature, value);
+ }
+ }
+ });
+ }
+
+ public ClassFileTransformer renameAndRemapField(String oldName, String newName) {
+ FieldSignaturePredicate matchPredicate = (name, signature) -> oldName.equals(name);
+ remapField(matchPredicate, newName);
+ return renameField(matchPredicate, newName);
+ }
+
/** Abstraction of the MethodVisitor.visitMethodInsn method with its sub visitor. */
@FunctionalInterface
public interface MethodInsnTransform {