Merge "Unique names in case of class merging conflicts"
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 01dcf0d..b05fd4f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.utils.InternalOptions;
import java.util.ListIterator;
import java.util.function.Predicate;
@@ -30,6 +31,7 @@
private final CallSiteInformation callSiteInformation;
private final Predicate<DexEncodedMethod> isProcessedConcurrently;
private final InliningInfo info;
+ private final InternalOptions options;
private final int inliningInstructionLimit;
private int instructionAllowance;
@@ -40,6 +42,7 @@
TypeEnvironment typeEnvironment,
CallSiteInformation callSiteInformation,
Predicate<DexEncodedMethod> isProcessedConcurrently,
+ InternalOptions options,
int inliningInstructionLimit,
int inliningInstructionAllowance) {
this.inliner = inliner;
@@ -49,6 +52,7 @@
this.callSiteInformation = callSiteInformation;
this.isProcessedConcurrently = isProcessedConcurrently;
info = Log.ENABLED ? new InliningInfo(method) : null;
+ this.options = options;
this.inliningInstructionLimit = inliningInstructionLimit;
this.instructionAllowance = inliningInstructionAllowance;
}
@@ -167,6 +171,11 @@
return false;
}
+ if (options.testing.validInliningReasons != null
+ && !options.testing.validInliningReasons.contains(reason)) {
+ return false;
+ }
+
// Abort inlining attempt if method -> target access is not right.
if (!inliner.hasInliningAccess(method, candidate)) {
if (info != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 22b14a4..44ebdc7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -413,6 +413,7 @@
typeEnvironment,
callSiteInformation,
isProcessedConcurrently,
+ options,
inliningInstructionLimit,
inliningInstructionAllowance);
}
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 eeaa9f3..c999cf3 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -30,7 +31,6 @@
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.optimize.InvokeSingleTargetExtractor;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.FieldSignatureEquivalence;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -57,6 +57,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -133,6 +134,12 @@
}
}
+ private enum Rename {
+ ALWAYS,
+ IF_NEEDED,
+ NEVER
+ }
+
private final DexApplication application;
private final AppInfoWithLiveness appInfo;
private final GraphLense graphLense;
@@ -603,20 +610,38 @@
Set<Wrapper<DexMethod>> existingMethods = new HashSet<>();
addAll(existingMethods, target.methods(), MethodSignatureEquivalence.get());
- List<DexEncodedMethod> directMethods = new ArrayList<>();
+ Map<Wrapper<DexMethod>, DexEncodedMethod> directMethods = new HashMap<>();
+ Map<Wrapper<DexMethod>, DexEncodedMethod> virtualMethods = new HashMap<>();
+
+ Predicate<DexMethod> availableMethodSignatures =
+ (method) -> {
+ Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method);
+ return !existingMethods.contains(wrapped)
+ && !directMethods.containsKey(wrapped)
+ && !virtualMethods.containsKey(wrapped);
+ };
+
for (DexEncodedMethod directMethod : source.directMethods()) {
if (directMethod.isInstanceInitializer()) {
- directMethods.add(renameConstructor(directMethod));
+ add(
+ directMethods,
+ renameConstructor(directMethod, availableMethodSignatures),
+ MethodSignatureEquivalence.get());
} else {
- directMethods.add(directMethod);
+ DexEncodedMethod resultingDirectMethod =
+ renameMethod(
+ directMethod,
+ availableMethodSignatures,
+ directMethod.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
+ add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
+ deferredRenamings.map(directMethod.method, resultingDirectMethod.method);
if (!directMethod.isStaticMethod()) {
- blockRedirectionOfSuperCalls(directMethod.method);
+ blockRedirectionOfSuperCalls(resultingDirectMethod.method);
}
}
}
- List<DexEncodedMethod> virtualMethods = new ArrayList<>();
for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
DexEncodedMethod shadowedBy = findMethodInTarget(virtualMethod);
if (shadowedBy != null) {
@@ -626,14 +651,20 @@
continue;
}
} else {
+ if (abortMerge) {
+ // If [virtualMethod] does not resolve to a single method in [target], abort.
+ return false;
+ }
+
// The method is not shadowed. If it is abstract, we can simply move it to the subclass.
// Non-abstract methods are handled below (they cannot simply be moved to the subclass as
// a virtual method, because they might be the target of an invoke-super instruction).
if (virtualMethod.accessFlags.isAbstract()) {
+ // Update the holder of [virtualMethod] using renameMethod().
DexEncodedMethod resultingVirtualMethod =
- renameMethod(virtualMethod, target.type, false);
+ renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
- virtualMethods.add(resultingVirtualMethod);
+ add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
continue;
}
}
@@ -641,9 +672,10 @@
// This virtual method could be called directly from a sub class via an invoke-super
// instruction. Therefore, we translate this virtual method into a direct method, such that
// relevant invoke-super instructions can be rewritten into invoke-direct instructions.
- DexEncodedMethod resultingDirectMethod = renameMethod(virtualMethod, target.type, true);
+ DexEncodedMethod resultingDirectMethod =
+ renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS);
makePrivate(resultingDirectMethod);
- directMethods.add(resultingDirectMethod);
+ add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
// Record that invoke-super instructions in the target class should be redirected to the
// newly created direct method.
@@ -657,7 +689,7 @@
// it turns out that the method is never used, it will be removed by the final round
// of tree shaking.
shadowedBy = buildBridgeMethod(virtualMethod, resultingDirectMethod.method);
- virtualMethods.add(shadowedBy);
+ add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
}
deferredRenamings.map(virtualMethod.method, shadowedBy.method);
@@ -665,41 +697,32 @@
Collection<DexEncodedMethod> mergedDirectMethods =
mergeItems(
- directMethods.iterator(),
+ directMethods.values().iterator(),
target.directMethods(),
MethodSignatureEquivalence.get(),
- existingMethods,
- (existing, method) -> {
- DexEncodedMethod renamedMethod = renameMethod(method, target.type, true);
- deferredRenamings.map(method.method, renamedMethod.method);
- blockRedirectionOfSuperCalls(renamedMethod.method);
- return renamedMethod;
- });
+ this::resolveMethodConflict);
Collection<DexEncodedMethod> mergedVirtualMethods =
mergeItems(
- virtualMethods.iterator(),
+ virtualMethods.values().iterator(),
target.virtualMethods(),
MethodSignatureEquivalence.get(),
- existingMethods,
- this::abortOnNonAbstract);
+ this::resolveMethodConflict);
if (abortMerge) {
return false;
}
// Step 2: Merge fields
- Set<Wrapper<DexField>> existingFields = new HashSet<>();
- addAll(existingFields, target.fields(), FieldSignatureEquivalence.get());
- Collection<DexEncodedField> mergedStaticFields = mergeItems(
- Iterators.forArray(source.staticFields()),
- target.staticFields(),
- FieldSignatureEquivalence.get(),
- existingFields,
- this::renameField);
- Collection<DexEncodedField> mergedInstanceFields = mergeItems(
- Iterators.forArray(source.instanceFields()),
- target.instanceFields(),
- FieldSignatureEquivalence.get(),
- existingFields,
- this::renameField);
+ Collection<DexEncodedField> mergedStaticFields =
+ mergeItems(
+ Iterators.forArray(source.staticFields()),
+ target.staticFields(),
+ FieldSignatureEquivalence.get(),
+ this::resolveFieldConflict);
+ Collection<DexEncodedField> mergedInstanceFields =
+ mergeItems(
+ Iterators.forArray(source.instanceFields()),
+ target.instanceFields(),
+ FieldSignatureEquivalence.get(),
+ this::resolveFieldConflict);
// Step 3: Merge interfaces
Set<DexType> interfaces = mergeArrays(target.interfaces.values, source.interfaces.values);
// Now destructively update the class.
@@ -845,6 +868,11 @@
return null;
}
+ private <T extends KeyedDexItem<S>, S extends PresortedComparable<S>> void add(
+ Map<Wrapper<S>, T> map, T item, Equivalence<S> equivalence) {
+ map.put(equivalence.wrap(item.getKey()), item);
+ }
+
private <T extends KeyedDexItem<S>, S extends PresortedComparable<S>> void addAll(
Collection<Wrapper<S>> collection, Iterable<T> items, Equivalence<S> equivalence) {
for (T item : items) {
@@ -863,78 +891,71 @@
Iterator<S> fromItems,
S[] toItems,
Equivalence<T> equivalence,
- Set<Wrapper<T>> existing,
- BiFunction<S, S, S> onConflict) {
- HashMap<Wrapper<T>, S> methods = new HashMap<>();
+ BiFunction<S, Predicate<T>, S> onConflict) {
+ Map<Wrapper<T>, S> map = new HashMap<>();
// First add everything from the target class. These items are not preprocessed.
for (S item : toItems) {
- methods.put(equivalence.wrap(item.getKey()), item);
+ map.put(equivalence.wrap(item.getKey()), item);
}
- // Now add the new methods, resolving shadowing.
- addNonShadowed(fromItems, methods, equivalence, existing, onConflict);
- return methods.values();
+ // Now add the new items, resolving shadowing.
+ addNonShadowed(fromItems, map, equivalence, onConflict);
+ return map.values();
}
private <T extends PresortedComparable<T>, S extends KeyedDexItem<T>> void addNonShadowed(
Iterator<S> items,
- HashMap<Wrapper<T>, S> map,
+ Map<Wrapper<T>, S> map,
Equivalence<T> equivalence,
- Set<Wrapper<T>> existing,
- BiFunction<S, S, S> onConflict) {
+ BiFunction<S, Predicate<T>, S> onConflict) {
+ Predicate<T> availableSignatures = key -> !map.containsKey(equivalence.wrap(key));
while (items.hasNext()) {
S item = items.next();
- if (item == null) {
- // This item was filtered out by a preprocessing.
- continue;
- }
+ assert item != null;
Wrapper<T> wrapped = equivalence.wrap(item.getKey());
- if (existing.contains(wrapped)) {
- S resolved = onConflict.apply(map.get(wrapped), item);
+ if (map.containsKey(wrapped)) {
+ S resolved = onConflict.apply(item, availableSignatures);
+ assert availableSignatures.test(resolved.getKey());
wrapped = equivalence.wrap(resolved.getKey());
map.put(wrapped, resolved);
+ assert !availableSignatures.test(resolved.getKey());
} else {
map.put(wrapped, item);
}
}
}
- // Note that names returned by this function are not necessarily unique. However, class merging
- // is aborted if it turns out that the returned name is not unique.
- // TODO(christofferqa): Write a test that checks that class merging is aborted if this function
- // generates a name that is not unique.
- private DexString getFreshName(String nameString, DexType holder) {
+ // Note that names returned by this function are not necessarily unique. Clients should
+ // repeatedly try to generate a fresh name until it is unique.
+ private DexString getFreshName(String nameString, int index, DexType holder) {
String freshName = nameString + "$" + holder.toSourceString().replace('.', '$');
+ if (index > 1) {
+ freshName += index;
+ }
return application.dexItemFactory.createString(freshName);
}
- private DexEncodedMethod abortOnNonAbstract(DexEncodedMethod existing,
- DexEncodedMethod method) {
- // Ignore if we override a bridge method that would bridge to the superclasses method.
- if (existing != null && existing.accessFlags.isBridge()) {
- InvokeSingleTargetExtractor extractor = new InvokeSingleTargetExtractor();
- existing.getCode().registerCodeReferences(extractor);
- if (extractor.getTarget() == method.method) {
- return method;
- }
- }
-
- // Abstract shadowed methods are removed prior to calling mergeItems.
- assert !method.accessFlags.isAbstract();
-
- // If [existing] is null, there is a conflict between a static and virtual method. Otherwise,
- // there is a non-abstract method that is shadowed. We translate virtual shadowed methods into
- // direct methods with a fresh name, so this situation should never happen. We currently get
- // in this situation if getFreshName returns a name that is not unique, though.
+ private DexEncodedMethod resolveMethodConflict(
+ DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures) {
+ assert false;
abortMerge = true;
return method;
}
- private DexEncodedMethod renameConstructor(DexEncodedMethod method) {
+ private DexEncodedMethod renameConstructor(
+ DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures) {
assert method.isInstanceInitializer();
- DexType holder = method.method.holder;
- DexEncodedMethod result =
- method.toRenamedMethod(
- getFreshName(CONSTRUCTOR_NAME, holder), application.dexItemFactory);
+ DexType oldHolder = method.method.holder;
+
+ DexMethod newSignature;
+ int count = 1;
+ do {
+ DexString newName = getFreshName(CONSTRUCTOR_NAME, count, oldHolder);
+ newSignature =
+ application.dexItemFactory.createMethod(target.type, method.method.proto, newName);
+ count++;
+ } while (!availableMethodSignatures.test(newSignature));
+
+ DexEncodedMethod result = method.toTypeSubstitutedMethod(newSignature);
result.markForceInline();
deferredRenamings.map(method.method, result.method);
// Renamed constructors turn into ordinary private functions. They can be private, as
@@ -945,25 +966,60 @@
}
private DexEncodedMethod renameMethod(
- DexEncodedMethod method, DexType newHolder, boolean useFreshName) {
+ DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures, Rename strategy) {
// We cannot handle renaming static initializers yet and constructors should have been
// renamed already.
- assert !method.accessFlags.isConstructor();
- DexType oldHolder = method.method.holder;
+ assert !method.accessFlags.isConstructor() || strategy == Rename.NEVER;
DexString oldName = method.method.name;
- DexString newName =
- useFreshName ? getFreshName(oldName.toSourceString(), oldHolder) : oldName;
- DexMethod newSignature =
- application.dexItemFactory.createMethod(newHolder, method.method.proto, newName);
+ DexType oldHolder = method.method.holder;
+
+ DexMethod newSignature;
+ switch (strategy) {
+ case IF_NEEDED:
+ newSignature =
+ application.dexItemFactory.createMethod(target.type, method.method.proto, oldName);
+ if (availableMethodSignatures.test(newSignature)) {
+ break;
+ }
+ // Fall-through to ALWAYS so that we assign a new name.
+
+ case ALWAYS:
+ int count = 1;
+ do {
+ DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
+ newSignature =
+ application.dexItemFactory.createMethod(target.type, method.method.proto, newName);
+ count++;
+ } while (!availableMethodSignatures.test(newSignature));
+ break;
+
+ case NEVER:
+ newSignature =
+ application.dexItemFactory.createMethod(target.type, method.method.proto, oldName);
+ assert availableMethodSignatures.test(newSignature);
+ break;
+
+ default:
+ throw new Unreachable();
+ }
+
return method.toTypeSubstitutedMethod(newSignature);
}
- private DexEncodedField renameField(DexEncodedField existing, DexEncodedField field) {
+ private DexEncodedField resolveFieldConflict(
+ DexEncodedField field, Predicate<DexField> availableFieldSignatures) {
DexString oldName = field.field.name;
DexType oldHolder = field.field.clazz;
- DexString newName = getFreshName(oldName.toSourceString(), oldHolder);
- DexField newSignature =
- application.dexItemFactory.createField(target.type, field.field.type, newName);
+
+ DexField newSignature;
+ int count = 1;
+ do {
+ DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
+ newSignature =
+ application.dexItemFactory.createField(target.type, field.field.type, newName);
+ count++;
+ } while (!availableFieldSignatures.test(newSignature));
+
DexEncodedField result = field.toTypeSubstitutedField(newSignature);
deferredRenamings.map(field.field, result.field);
return result;
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 5f0ec45..ec54641 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
@@ -446,6 +447,7 @@
public boolean placeExceptionalBlocksLast = false;
public boolean dontCreateMarkerInD8 = false;
public boolean forceJumboStringProcessing = false;
+ public Set<Inliner.Reason> validInliningReasons = null;
}
public boolean canUseInvokePolymorphicOnVarHandle() {
diff --git a/src/test/examples/classmerging/ConflictInGeneratedNameTest.java b/src/test/examples/classmerging/ConflictInGeneratedNameTest.java
new file mode 100644
index 0000000..6d566ec
--- /dev/null
+++ b/src/test/examples/classmerging/ConflictInGeneratedNameTest.java
@@ -0,0 +1,113 @@
+// 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 ConflictInGeneratedNameTest {
+ public static void main(String[] args) {
+ B obj = new B();
+ obj.run();
+ }
+
+ public static class A {
+ private String name = "A";
+
+ public A() {
+ print("In A.<init>()");
+ constructor$classmerging$ConflictInGeneratedNameTest$A();
+ }
+
+ private void constructor$classmerging$ConflictInGeneratedNameTest$A() {
+ print("In A.constructor$classmerging$ConflictInGeneratedNameTest$A()");
+ }
+
+ public void printState() {
+ print("In A.printState()");
+ print("A=" + name + "=" + getName());
+ }
+
+ // This method is not overridden.
+ public void foo() {
+ print("In A.foo()");
+ }
+
+ // This method is overridden.
+ public void bar() {
+ print("In A.bar()");
+ }
+
+ // This method is overridden and there is a PUBLIC method in B with the same name
+ // as the direct version of this method in B.
+ public void baz() {
+ print("In A.baz()");
+ }
+
+ // This method is overridden and there is a PRIVATE method in B with the same name
+ // as the direct version of this method in B.
+ public void boo() {
+ print("In A.boo()");
+ }
+
+ // There is a private method in B with the same name as this one.
+ private String getName() {
+ return name;
+ }
+ }
+
+ public static class B extends A {
+ private String name = "B";
+ private String name$classmerging$ConflictInGeneratedNameTest$A = "C";
+
+ public B() {
+ print("In B.<init>()");
+ print("A=" + super.name + "=" + super.getName());
+ print("B=" + name + "=" + getName());
+ print("C=" + name$classmerging$ConflictInGeneratedNameTest$A);
+ }
+
+ public void run() {
+ printState();
+ foo();
+ bar();
+ baz();
+ baz$classmerging$ConflictInGeneratedNameTest$A();
+ boo();
+ boo$classmerging$ConflictInGeneratedNameTest$A();
+ }
+
+ @Override
+ public void bar() {
+ print("In B.bar()");
+ super.bar();
+ }
+
+ @Override
+ public void baz() {
+ print("In B.baz()");
+ super.baz();
+ }
+
+ public void baz$classmerging$ConflictInGeneratedNameTest$A() {
+ print("In B.baz$classmerging$ConflictInGeneratedNameTest$A()");
+ }
+
+ @Override
+ public void boo() {
+ print("In B.boo()");
+ super.boo();
+ }
+
+ private void boo$classmerging$ConflictInGeneratedNameTest$A() {
+ print("In B.boo$classmerging$ConflictInGeneratedNameTest$A()");
+ }
+
+ private String getName() {
+ return name;
+ }
+ }
+
+ public static void print(String message) {
+ System.out.println(message);
+ }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 1a20ce3..fa48747 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.ConflictInGeneratedNameTest {
+ public static void main(...);
+}
-keep public class classmerging.ConflictingInterfaceSignaturesTest {
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 8f98fd3..7d200bd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -15,9 +15,13 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.MoveException;
import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.utils.AndroidApp;
@@ -37,12 +41,13 @@
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Predicate;
-import org.junit.Ignore;
import org.junit.Test;
+import org.junit.runner.RunWith;
// TODO(christofferqa): Add tests to check that statically typed invocations on method handles
// continue to work after class merging. Rewriting of method handles should be carried out by
// LensCodeRewriter.rewriteDexMethodHandle.
+@RunWith(VmTestRunner.class)
public class ClassMergingTest extends TestBase {
private static final Path CF_DIR =
@@ -108,6 +113,70 @@
}
@Test
+ public void testConflictInGeneratedName() throws Exception {
+ String main = "classmerging.ConflictInGeneratedNameTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ConflictInGeneratedNameTest.class"),
+ CF_DIR.resolve("ConflictInGeneratedNameTest$A.class"),
+ CF_DIR.resolve("ConflictInGeneratedNameTest$B.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.ConflictInGeneratedNameTest",
+ "classmerging.ConflictInGeneratedNameTest$B");
+ DexInspector inspector =
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ preservedClassNames::contains,
+ getProguardConfig(EXAMPLE_KEEP),
+ options -> {
+ configure(options);
+ // Avoid that direct methods in B get inlined.
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ });
+
+ ClassSubject clazzSubject = inspector.clazz("classmerging.ConflictInGeneratedNameTest$B");
+ assertThat(clazzSubject, isPresent());
+
+ String suffix = "$classmerging$ConflictInGeneratedNameTest$A";
+ List<String> EMPTY = ImmutableList.of();
+
+ // There should be three fields.
+ assertThat(clazzSubject.field("java.lang.String", "name"), isPresent());
+ assertThat(clazzSubject.field("java.lang.String", "name" + suffix), isPresent());
+ assertThat(clazzSubject.field("java.lang.String", "name" + suffix + "2"), isPresent());
+
+ // The direct method "constructor$classmerging$ConflictInGeneratedNameTest$A" is processed after
+ // the method "<init>" is renamed to exactly that name. Therefore the conflict should have been
+ // resolved by appending [suffix] to it.
+ assertThat(clazzSubject.method("void", "constructor" + suffix + suffix, EMPTY), isPresent());
+
+ // There should be two foo's.
+ assertThat(clazzSubject.method("void", "foo", EMPTY), isPresent());
+ assertThat(clazzSubject.method("void", "foo" + suffix, EMPTY), isPresent());
+
+ // There should be two bar's.
+ assertThat(clazzSubject.method("void", "bar", EMPTY), isPresent());
+ assertThat(clazzSubject.method("void", "bar" + suffix, EMPTY), isPresent());
+
+ // There should be three baz's.
+ assertThat(clazzSubject.method("void", "baz", EMPTY), isPresent());
+ assertThat(clazzSubject.method("void", "baz" + suffix, EMPTY), isPresent());
+ assertThat(clazzSubject.method("void", "baz" + suffix + "2", EMPTY), isPresent());
+
+ // There should be three boo's.
+ assertThat(clazzSubject.method("void", "boo", EMPTY), isPresent());
+ assertThat(clazzSubject.method("void", "boo" + suffix, EMPTY), isPresent());
+ assertThat(clazzSubject.method("void", "boo" + suffix + "2", EMPTY), isPresent());
+
+ // There should be two getName's.
+ assertThat(clazzSubject.method("java.lang.String", "getName", EMPTY), isPresent());
+ assertThat(clazzSubject.method("java.lang.String", "getName" + suffix, EMPTY), isPresent());
+ }
+
+ @Test
public void testConflictWasDetected() throws Exception {
runR8(EXAMPLE_KEEP, this::configure);
assertTrue(inspector.clazz("classmerging.ConflictingInterface").isPresent());
@@ -240,8 +309,8 @@
// public void invokeMethodOnE() { "invoke-super E.m()" }
// public void invokeMethodOnF() { "invoke-super F.m()" }
// }
- @Ignore
@Test
+ @IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
public void testSuperCallToMergedClassIsRewritten() throws Exception {
String main = "classmerging.SuperCallToMergedClassIsRewrittenTest";
Set<String> preservedClassNames =
@@ -523,7 +592,17 @@
private DexInspector runTestOnInput(
String main, AndroidApp input, Predicate<String> preservedClassNames, String proguardConfig)
throws Exception {
- AndroidApp output = compileWithR8(input, proguardConfig, this::configure);
+ return runTestOnInput(main, input, preservedClassNames, proguardConfig, this::configure);
+ }
+
+ private DexInspector runTestOnInput(
+ String main,
+ AndroidApp input,
+ Predicate<String> preservedClassNames,
+ String proguardConfig,
+ Consumer<InternalOptions> optionsConsumer)
+ throws Exception {
+ AndroidApp output = compileWithR8(input, proguardConfig, optionsConsumer);
DexInspector inputInspector = new DexInspector(input);
DexInspector outputInspector = new DexInspector(output);
// Check that all classes in [preservedClassNames] are in fact preserved.