Version 1.4.42
Cherry pick: Fix interface method minification bug
CL: https://r8-review.googlesource.com/c/r8/+/34402
Cherry pick: Reproduce interface method minification bug
CL: https://r8-review.googlesource.com/c/r8/+/34380
Cherry pick: Separate interface method minification from class method minification
CL: https://r8-review.googlesource.com/c/r8/+/34300
Cherry pick: Simplify interface method minification
CL: https://r8-review.googlesource.com/c/r8/+/34280
Cherry pick: Strengthen no duplicate methods check
CL: https://r8-review.googlesource.com/c/r8/+/34123
Cherry pick: Verify absence of duplicate methods
CL: https://r8-review.googlesource.com/c/r8/+/33930
Cherry pick: Disallow direct manipulation of method arrays
CL: https://r8-review.googlesource.com/c/r8/+/33929
Bug: 123730537
Change-Id: If8ca0780f693aeec4a39946e5a2a652db5abe0a8
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 89efb56..049854e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -611,15 +611,17 @@
new DiscardedChecker(rootSet, application, options).run();
}
- timing.begin("Minification");
- // If we did not have keep rules, everything will be marked as keep, so no minification
- // will happen. Just avoid the overhead.
- NamingLens namingLens =
- options.enableMinification
- ? new Minifier(appView.appInfo().withLiveness(), rootSet, desugaredCallSites, options)
- .run(timing)
- : NamingLens.getIdentityLens();
- timing.end();
+ // Perform minification.
+ NamingLens namingLens;
+ if (options.enableMinification) {
+ timing.begin("Minification");
+ namingLens =
+ new Minifier(appView.appInfo().withLiveness(), rootSet, desugaredCallSites, options)
+ .run(timing);
+ timing.end();
+ } else {
+ namingLens = NamingLens.getIdentityLens();
+ }
ProguardMapSupplier proguardMapSupplier;
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index 47ff5ee..6267db1 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -241,11 +241,11 @@
.filter(DexEncodedField::hasAnnotation)
.flatMap(f -> Arrays.stream(f.annotations.annotations));
Stream<DexAnnotation> virtualMethodAnnotations =
- Arrays.stream(classDef.virtualMethods())
+ classDef.virtualMethods().stream()
.filter(DexEncodedMethod::hasAnnotation)
.flatMap(m -> Arrays.stream(m.annotations.annotations));
Stream<DexAnnotation> directMethodAnnotations =
- Arrays.stream(classDef.directMethods())
+ classDef.directMethods().stream()
.filter(DexEncodedMethod::hasAnnotation)
.flatMap(m -> Arrays.stream(m.annotations.annotations));
Stream<DexAnnotation> classAnnotations = Arrays.stream(classDef.annotations.annotations);
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 27a34f7..0d8b672 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.41";
+ public static final String LABEL = "1.4.42";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 73f7921..e8ebbbb 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -563,7 +563,7 @@
}
private void writeEncodedFields(DexEncodedField[] fields) {
- assert PresortedComparable.isSorted(fields);
+ assert PresortedComparable.isSorted(Arrays.asList(fields));
int currentOffset = 0;
for (DexEncodedField field : fields) {
int nextOffset = mapping.getOffsetFor(field.field);
@@ -574,7 +574,7 @@
}
}
- private void writeEncodedMethods(DexEncodedMethod[] methods, boolean clearBodies) {
+ private void writeEncodedMethods(List<DexEncodedMethod> methods, boolean clearBodies) {
assert PresortedComparable.isSorted(methods);
int currentOffset = 0;
for (DexEncodedMethod method : methods) {
@@ -602,8 +602,8 @@
mixedSectionOffsets.setOffsetFor(clazz, dest.position());
dest.putUleb128(clazz.staticFields().length);
dest.putUleb128(clazz.instanceFields().length);
- dest.putUleb128(clazz.directMethods().length);
- dest.putUleb128(clazz.virtualMethods().length);
+ dest.putUleb128(clazz.directMethods().size());
+ dest.putUleb128(clazz.virtualMethods().size());
writeEncodedFields(clazz.staticFields());
writeEncodedFields(clazz.instanceFields());
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index fa5d8a8..1ef2f68 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.OrderedMergingIterator;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@@ -37,10 +38,11 @@
parameterAnnotations.add(method);
}
}
- assert isSorted(clazz.staticFields());
- assert isSorted(clazz.instanceFields());
+ assert isSorted(Arrays.asList(clazz.staticFields()));
+ assert isSorted(Arrays.asList(clazz.instanceFields()));
OrderedMergingIterator<DexEncodedField, DexField> fields =
- new OrderedMergingIterator<>(clazz.staticFields(), clazz.instanceFields());
+ new OrderedMergingIterator<>(
+ Arrays.asList(clazz.staticFields()), Arrays.asList(clazz.instanceFields()));
fieldAnnotations = new ArrayList<>();
while (fields.hasNext()) {
DexEncodedField field = fields.next();
@@ -108,11 +110,13 @@
throw new Unreachable();
}
- private static <T extends PresortedComparable<T>> boolean isSorted(KeyedDexItem<T>[] items) {
+ private static <T extends PresortedComparable<T>> boolean isSorted(
+ List<? extends KeyedDexItem<T>> items) {
return isSorted(items, KeyedDexItem::getKey);
}
- private static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
+ private static <S, T extends Comparable<T>> boolean isSorted(
+ List<S> items, Function<S, T> getter) {
T current = null;
for (S item : items) {
T next = getter.apply(item);
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index 21900a01..19d7e24 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -11,7 +11,6 @@
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Arrays;
import java.util.function.Consumer;
public abstract class DexByteCodeWriter {
@@ -76,8 +75,8 @@
private boolean anyMethodMatches(DexClass clazz) {
return !options.hasMethodsFilter()
- || Arrays.stream(clazz.virtualMethods()).anyMatch(options::methodMatchesFilter)
- || Arrays.stream(clazz.directMethods()).anyMatch(options::methodMatchesFilter);
+ || clazz.virtualMethods().stream().anyMatch(options::methodMatchesFilter)
+ || clazz.directMethods().stream().anyMatch(options::methodMatchesFilter);
}
private void writeClass(DexProgramClass clazz, PrintStream ps) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index b229074..828d98d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,10 +8,14 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -19,6 +23,11 @@
public abstract class DexClass extends DexDefinition {
+ public interface MethodSetter {
+
+ void setMethod(int index, DexEncodedMethod method);
+ }
+
private static final DexEncodedMethod[] NO_METHODS = {};
private static final DexEncodedField[] NO_FIELDS = {};
@@ -29,22 +38,17 @@
public DexTypeList interfaces;
public DexString sourceFile;
- /**
- * Access has to be synchronized during concurrent collection/writing phase.
- */
- protected DexEncodedField[] staticFields;
- /**
- * Access has to be synchronized during concurrent collection/writing phase.
- */
- protected DexEncodedField[] instanceFields;
- /**
- * Access has to be synchronized during concurrent collection/writing phase.
- */
- protected DexEncodedMethod[] directMethods;
- /**
- * Access has to be synchronized during concurrent collection/writing phase.
- */
- protected DexEncodedMethod[] virtualMethods;
+ /** Access has to be synchronized during concurrent collection/writing phase. */
+ protected DexEncodedField[] staticFields = NO_FIELDS;
+
+ /** Access has to be synchronized during concurrent collection/writing phase. */
+ protected DexEncodedField[] instanceFields = NO_FIELDS;
+
+ /** Access has to be synchronized during concurrent collection/writing phase. */
+ protected DexEncodedMethod[] directMethods = NO_METHODS;
+
+ /** Access has to be synchronized during concurrent collection/writing phase. */
+ protected DexEncodedMethod[] virtualMethods = NO_METHODS;
/** Enclosing context of this class if it is an inner class, null otherwise. */
private EnclosingMethodAttribute enclosingMethod;
@@ -124,20 +128,129 @@
throw new Unreachable();
}
- public DexEncodedMethod[] directMethods() {
- return directMethods;
+ public List<DexEncodedMethod> directMethods() {
+ assert directMethods != null;
+ if (InternalOptions.assertionsEnabled()) {
+ return Collections.unmodifiableList(Arrays.asList(directMethods));
+ }
+ return Arrays.asList(directMethods);
+ }
+
+ public void appendDirectMethod(DexEncodedMethod method) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1];
+ System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
+ newMethods[directMethods.length] = method;
+ directMethods = newMethods;
+ assert verifyCorrectnessOfMethodHolder(method);
+ assert verifyNoDuplicateMethods();
+ }
+
+ public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + methods.size()];
+ System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
+ int i = directMethods.length;
+ for (DexEncodedMethod method : methods) {
+ newMethods[i] = method;
+ i++;
+ }
+ directMethods = newMethods;
+ assert verifyCorrectnessOfMethodHolders(methods);
+ assert verifyNoDuplicateMethods();
+ }
+
+ public void removeDirectMethod(int index) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length - 1];
+ System.arraycopy(directMethods, 0, newMethods, 0, index);
+ System.arraycopy(directMethods, index + 1, newMethods, index, directMethods.length - index - 1);
+ directMethods = newMethods;
+ }
+
+ public void setDirectMethod(int index, DexEncodedMethod method) {
+ directMethods[index] = method;
+ assert verifyCorrectnessOfMethodHolder(method);
+ assert verifyNoDuplicateMethods();
}
public void setDirectMethods(DexEncodedMethod[] values) {
directMethods = MoreObjects.firstNonNull(values, NO_METHODS);
+ assert verifyCorrectnessOfMethodHolders(directMethods());
+ assert verifyNoDuplicateMethods();
}
- public DexEncodedMethod[] virtualMethods() {
- return virtualMethods;
+ public List<DexEncodedMethod> virtualMethods() {
+ assert virtualMethods != null;
+ if (InternalOptions.assertionsEnabled()) {
+ return Collections.unmodifiableList(Arrays.asList(virtualMethods));
+ }
+ return Arrays.asList(virtualMethods);
+ }
+
+ public void appendVirtualMethod(DexEncodedMethod method) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
+ System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
+ newMethods[virtualMethods.length] = method;
+ virtualMethods = newMethods;
+ assert verifyCorrectnessOfMethodHolder(method);
+ assert verifyNoDuplicateMethods();
+ }
+
+ public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()];
+ System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
+ int i = virtualMethods.length;
+ for (DexEncodedMethod method : methods) {
+ newMethods[i] = method;
+ i++;
+ }
+ virtualMethods = newMethods;
+ assert verifyCorrectnessOfMethodHolders(methods);
+ assert verifyNoDuplicateMethods();
+ }
+
+ public void removeVirtualMethod(int index) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length - 1];
+ System.arraycopy(virtualMethods, 0, newMethods, 0, index);
+ System.arraycopy(
+ virtualMethods, index + 1, newMethods, index, virtualMethods.length - index - 1);
+ virtualMethods = newMethods;
+ }
+
+ public void setVirtualMethod(int index, DexEncodedMethod method) {
+ virtualMethods[index] = method;
+ assert verifyCorrectnessOfMethodHolder(method);
+ assert verifyNoDuplicateMethods();
}
public void setVirtualMethods(DexEncodedMethod[] values) {
virtualMethods = MoreObjects.firstNonNull(values, NO_METHODS);
+ assert verifyCorrectnessOfMethodHolders(virtualMethods());
+ assert verifyNoDuplicateMethods();
+ }
+
+ private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) {
+ assert method.method.holder == type
+ : "Expected method `"
+ + method.method.toSourceString()
+ + "` to have holder `"
+ + type.toSourceString()
+ + "`";
+ return true;
+ }
+
+ private boolean verifyCorrectnessOfMethodHolders(Iterable<DexEncodedMethod> methods) {
+ for (DexEncodedMethod method : methods) {
+ assert verifyCorrectnessOfMethodHolder(method);
+ }
+ return true;
+ }
+
+ private boolean verifyNoDuplicateMethods() {
+ Set<DexMethod> unique = Sets.newIdentityHashSet();
+ for (DexEncodedMethod method : methods()) {
+ boolean changed = unique.add(method.method);
+ assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
+ }
+ return true;
}
public void forEachMethod(Consumer<DexEncodedMethod> consumer) {
@@ -289,14 +402,14 @@
* Find direct method in this class matching method.
*/
public DexEncodedMethod lookupDirectMethod(DexMethod method) {
- return lookupTarget(directMethods(), method);
+ return lookupTarget(directMethods, method);
}
/**
* Find virtual method in this class matching method.
*/
public DexEncodedMethod lookupVirtualMethod(DexMethod method) {
- return lookupTarget(virtualMethods(), method);
+ return lookupTarget(virtualMethods, method);
}
/**
@@ -377,7 +490,9 @@
}
public DexEncodedMethod getClassInitializer() {
- return Arrays.stream(directMethods()).filter(DexEncodedMethod::isClassInitializer).findAny()
+ return Arrays.stream(directMethods)
+ .filter(DexEncodedMethod::isClassInitializer)
+ .findAny()
.orElse(null);
}
@@ -543,7 +658,9 @@
public boolean isValid() {
assert !isInterface()
- || Arrays.stream(virtualMethods()).noneMatch(method -> method.accessFlags.isFinal());
+ || Arrays.stream(virtualMethods).noneMatch(method -> method.accessFlags.isFinal());
+ assert verifyCorrectnessOfMethodHolders(methods());
+ assert verifyNoDuplicateMethods();
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index dafb07c..57b08fb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -226,7 +226,7 @@
public boolean hasMethodsOrFields() {
int numberOfFields = staticFields().length + instanceFields().length;
- int numberOfMethods = directMethods().length + virtualMethods().length;
+ int numberOfMethods = directMethods().size() + virtualMethods().size();
return numberOfFields + numberOfMethods > 0;
}
@@ -269,7 +269,7 @@
// It does not actually hurt to compute this multiple times. So racing on staticValues is OK.
if (staticValues == SENTINEL_NOT_YET_COMPUTED) {
synchronized (staticFields) {
- assert PresortedComparable.isSorted(staticFields);
+ assert PresortedComparable.isSorted(Arrays.asList(staticFields));
DexEncodedField[] fields = staticFields;
int length = 0;
List<DexValue> values = new ArrayList<>(fields.length);
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 0c48ea9..9182811 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -239,25 +240,23 @@
* language, where interfaces "extend" their superinterface.
*/
public void forAllImplementsSubtypes(Consumer<DexType> f) {
- if (hierarchyLevel != INTERFACE_LEVEL) {
- return;
+ allImplementsSubtypes().forEach(f);
+ }
+
+ public Iterable<DexType> allImplementsSubtypes() {
+ if (hierarchyLevel == INTERFACE_LEVEL) {
+ return Iterables.filter(directSubtypes, subtype -> !subtype.isInterface());
}
- for (DexType subtype : directSubtypes) {
- // Filter out other interfaces.
- if (subtype.hierarchyLevel != INTERFACE_LEVEL) {
- f.accept(subtype);
- }
- }
+ return ImmutableList.of();
+ }
+
+ public static Iterable<DexType> allInterfaces(DexItemFactory dexItemFactory) {
+ assert dexItemFactory.objectType.hierarchyLevel == ROOT_LEVEL;
+ return Iterables.filter(dexItemFactory.objectType.directSubtypes, DexType::isInterface);
}
public static void forAllInterfaces(DexItemFactory factory, Consumer<DexType> f) {
- DexType object = factory.objectType;
- assert object.hierarchyLevel == ROOT_LEVEL;
- for (DexType subtype : object.directSubtypes) {
- if (subtype.isInterface()) {
- f.accept(subtype);
- }
- }
+ allInterfaces(factory).forEach(f);
}
/**
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
index a4ee2ee..563b40a 100644
--- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
+++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -4,15 +4,23 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.naming.NamingLens;
+import java.util.Arrays;
+import java.util.List;
import java.util.function.Function;
public interface PresortedComparable<T> extends Presorted, Comparable<T> {
- static <T extends PresortedComparable<T>> boolean isSorted(KeyedDexItem<T>[] items) {
+ static <T extends PresortedComparable<T>> boolean isSorted(
+ List<? extends KeyedDexItem<T>> items) {
return isSorted(items, KeyedDexItem::getKey);
}
static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
+ return isSorted(Arrays.asList(items), getter);
+ }
+
+ static <S, T extends Comparable<T>> boolean isSorted(
+ List<? extends S> items, Function<S, T> getter) {
T current = null;
for (S item : items) {
T next = getter.apply(item);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index d2a233b..968b200 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
@@ -85,17 +84,14 @@
}
// Add the methods.
- DexEncodedMethod[] existing = clazz.virtualMethods();
- clazz.setVirtualMethods(new DexEncodedMethod[existing.length + methodsToImplement.size()]);
- System.arraycopy(existing, 0, clazz.virtualMethods(), 0, existing.length);
-
- for (int i = 0; i < methodsToImplement.size(); i++) {
- DexEncodedMethod method = methodsToImplement.get(i);
+ List<DexEncodedMethod> newForwardingMethods = new ArrayList<>(methodsToImplement.size());
+ for (DexEncodedMethod method : methodsToImplement) {
assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
- clazz.virtualMethods()[existing.length + i] = newMethod;
+ newForwardingMethods.add(newMethod);
createdMethods.put(newMethod, method);
}
+ clazz.appendVirtualMethods(newForwardingMethods);
}
private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
@@ -148,7 +144,7 @@
helper.merge(rewriter.getOrCreateInterfaceInfo(clazz, current, type));
}
- accumulatedVirtualMethods.addAll(Arrays.asList(clazz.virtualMethods()));
+ accumulatedVirtualMethods.addAll(clazz.virtualMethods());
List<DexEncodedMethod> defaultMethodsInDirectInterface = helper.createFullList();
@@ -201,7 +197,7 @@
current = clazz;
while (true) {
// Hide candidates by virtual method of the class.
- hideCandidates(Arrays.asList(current.virtualMethods()), candidates, toBeImplemented);
+ hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
if (candidates.isEmpty()) {
return toBeImplemented;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 955abf0..2219cf8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -96,16 +96,7 @@
method.annotations.keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation));
}
// Add the newly constructed methods to the class.
- DexEncodedMethod[] oldVirtualMethods = clazz.virtualMethods();
- DexEncodedMethod[] newVirtualMethods =
- new DexEncodedMethod[oldVirtualMethods.length + covariantReturnTypeMethods.size()];
- System.arraycopy(oldVirtualMethods, 0, newVirtualMethods, 0, oldVirtualMethods.length);
- int i = oldVirtualMethods.length;
- for (DexEncodedMethod syntheticMethod : covariantReturnTypeMethods) {
- newVirtualMethods[i] = syntheticMethod;
- i++;
- }
- clazz.setVirtualMethods(newVirtualMethods);
+ clazz.appendVirtualMethods(covariantReturnTypeMethods);
}
// Processes all the dalvik.annotation.codegen.CovariantReturnType and dalvik.annotation.codegen.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index afe0c8e..9fe23de 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -114,7 +114,7 @@
}
// If at least one bridge method was removed then update the table.
- if (remainingMethods.size() < iface.virtualMethods().length) {
+ if (remainingMethods.size() < iface.virtualMethods().size()) {
iface.setVirtualMethods(remainingMethods.toArray(
new DexEncodedMethod[remainingMethods.size()]));
}
@@ -170,7 +170,7 @@
}
}
}
- if (remainingMethods.size() < iface.directMethods().length) {
+ if (remainingMethods.size() < iface.directMethods().size()) {
iface.setDirectMethods(remainingMethods.toArray(
new DexEncodedMethod[remainingMethods.size()]));
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index b78f6df..a0bc184 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -513,9 +513,9 @@
DexMethod implMethod = descriptor.implHandle.asMethod();
DexClass implMethodHolder = definitionFor(implMethod.holder);
- DexEncodedMethod[] directMethods = implMethodHolder.directMethods();
- for (int i = 0; i < directMethods.length; i++) {
- DexEncodedMethod encodedMethod = directMethods[i];
+ List<DexEncodedMethod> directMethods = implMethodHolder.directMethods();
+ for (int i = 0; i < directMethods.size(); i++) {
+ DexEncodedMethod encodedMethod = directMethods.get(i);
if (implMethod.match(encodedMethod)) {
// We need to create a new static method with the same code to be able to safely
// relax its accessibility without making it virtual.
@@ -539,7 +539,7 @@
dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
assert (dexCode.getDebugInfo() == null)
|| (callTarget.getArity() == dexCode.getDebugInfo().parameters.length);
- directMethods[i] = newMethod;
+ implMethodHolder.setDirectMethod(i, newMethod);
return true;
}
}
@@ -579,20 +579,11 @@
// We may arrive here concurrently so we need must update the methods of the class atomically.
synchronized (accessorClass) {
- accessorClass.setDirectMethods(
- appendMethod(accessorClass.directMethods(), accessorEncodedMethod));
+ accessorClass.appendDirectMethod(accessorEncodedMethod);
}
rewriter.converter.optimizeSynthesizedMethod(accessorEncodedMethod);
return true;
}
-
- private DexEncodedMethod[] appendMethod(DexEncodedMethod[] methods, DexEncodedMethod method) {
- int size = methods.length;
- DexEncodedMethod[] newMethods = new DexEncodedMethod[size + 1];
- System.arraycopy(methods, 0, newMethods, 0, size);
- newMethods[size] = method;
- return newMethods;
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index eba995f..ade6feb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -128,33 +128,26 @@
/** Remove lambda deserialization methods. */
public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
- boolean anyRemoved = false;
for (DexProgramClass clazz : classes) {
// Search for a lambda deserialization method and remove it if found.
- DexEncodedMethod[] directMethods = clazz.directMethods();
+ List<DexEncodedMethod> directMethods = clazz.directMethods();
if (directMethods != null) {
- int methodCount = directMethods.length;
+ int methodCount = directMethods.size();
for (int i = 0; i < methodCount; i++) {
- DexEncodedMethod encoded = directMethods[i];
+ DexEncodedMethod encoded = directMethods.get(i);
DexMethod method = encoded.method;
if (method.isLambdaDeserializeMethod(appInfo.dexItemFactory)) {
assert encoded.accessFlags.isStatic();
assert encoded.accessFlags.isSynthetic();
-
- DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
- System.arraycopy(directMethods, 0, newMethods, 0, i);
- System.arraycopy(directMethods, i + 1, newMethods, i, methodCount - i - 1);
- clazz.setDirectMethods(newMethods);
-
- anyRemoved = true;
+ clazz.removeDirectMethod(i);
// We assume there is only one such method in the class.
- break;
+ return true;
}
}
}
}
- return anyRemoved;
+ return false;
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 6bd668c..c83602a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -178,9 +178,9 @@
}
// Change the return type of direct methods that return an uninstantiated type to void.
- DexEncodedMethod[] directMethods = clazz.directMethods();
- for (int i = 0; i < directMethods.length; ++i) {
- DexEncodedMethod encodedMethod = directMethods[i];
+ List<DexEncodedMethod> directMethods = clazz.directMethods();
+ for (int i = 0; i < directMethods.size(); ++i) {
+ DexEncodedMethod encodedMethod = directMethods.get(i);
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
prototypeChangesPerMethod.getOrDefault(
@@ -193,7 +193,7 @@
// TODO(b/110806787): Can be extended to handle collisions by renaming the given
// method.
if (usedSignatures.add(wrapper)) {
- directMethods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ clazz.setDirectMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
methodMapping.put(method, newMethod);
if (removedArgumentsInfo.hasRemovedArguments()) {
removedArgumentsInfoPerMethod.put(newMethod, removedArgumentsInfo);
@@ -208,9 +208,9 @@
// all supertypes of the current class are always visited prior to the current class.
// This is important to ensure that a method that used to override a method in its super
// class will continue to do so after this optimization.
- DexEncodedMethod[] virtualMethods = clazz.virtualMethods();
- for (int i = 0; i < virtualMethods.length; ++i) {
- DexEncodedMethod encodedMethod = virtualMethods[i];
+ List<DexEncodedMethod> virtualMethods = clazz.virtualMethods();
+ for (int i = 0; i < virtualMethods.size(); ++i) {
+ DexEncodedMethod encodedMethod = virtualMethods.get(i);
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
@@ -228,13 +228,13 @@
boolean signatureIsAvailable = usedSignatures.add(wrapper);
assert signatureIsAvailable;
- virtualMethods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ clazz.setVirtualMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
methodMapping.put(method, newMethod);
}
}
}
- for (int i = 0; i < virtualMethods.length; ++i) {
- DexEncodedMethod encodedMethod = virtualMethods[i];
+ for (int i = 0; i < virtualMethods.size(); ++i) {
+ DexEncodedMethod encodedMethod = virtualMethods.get(i);
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
@@ -248,7 +248,7 @@
if (!methodPool.hasSeen(wrapper) && usedSignatures.add(wrapper)) {
methodPool.seen(wrapper);
- virtualMethods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ clazz.setVirtualMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
methodMapping.put(method, newMethod);
boolean added =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index f0aa91a..7056d32 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -184,12 +184,13 @@
for (DexEncodedMethod method : clazz.methods()) {
signatures.markSignatureAsUsed(method.method);
}
- for (int i = 0; i < clazz.directMethods().length; i++) {
- DexEncodedMethod method = clazz.directMethods()[i];
+ List<DexEncodedMethod> directMethods = clazz.directMethods();
+ for (int i = 0; i < directMethods.size(); i++) {
+ DexEncodedMethod method = directMethods.get(i);
RemovedArgumentsInfo unused = collectUnusedArguments(method);
DexEncodedMethod newMethod = signatures.removeArguments(method, unused);
if (newMethod != null) {
- clazz.directMethods()[i] = newMethod;
+ clazz.setDirectMethod(i, newMethod);
synchronized (this) {
methodMapping.put(method.method, newMethod.method);
removedArguments.put(newMethod.method, unused);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 8469004..234bcc6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -144,8 +144,7 @@
}
void validateDirectMethods(DexClass lambda) throws LambdaStructureError {
- DexEncodedMethod[] directMethods = lambda.directMethods();
- for (DexEncodedMethod method : directMethods) {
+ for (DexEncodedMethod method : lambda.directMethods()) {
if (method.isClassInitializer()) {
// We expect to see class initializer only if there is a singleton field.
if (lambda.staticFields().length != 1) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 6dac0a0..ff4a12e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -545,16 +545,13 @@
}
// Process static methods.
- if (candidateClass.directMethods().length > 0) {
- DexEncodedMethod[] oldMethods = hostClass.directMethods();
- DexEncodedMethod[] extraMethods = candidateClass.directMethods();
- DexEncodedMethod[] newMethods = new DexEncodedMethod[oldMethods.length + extraMethods.length];
- System.arraycopy(oldMethods, 0, newMethods, 0, oldMethods.length);
- for (int i = 0; i < extraMethods.length; i++) {
- DexEncodedMethod method = extraMethods[i];
+ List<DexEncodedMethod> extraMethods = candidateClass.directMethods();
+ if (!extraMethods.isEmpty()) {
+ List<DexEncodedMethod> newMethods = new ArrayList<>(extraMethods.size());
+ for (DexEncodedMethod method : extraMethods) {
DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(
factory().createMethod(hostType, method.method.proto, method.method.name));
- newMethods[oldMethods.length + i] = newMethod;
+ newMethods.add(newMethod);
staticizedMethods.add(newMethod);
staticizedMethods.remove(method);
DexMethod originalMethod = methodMapping.inverse().get(method.method);
@@ -564,7 +561,7 @@
methodMapping.put(originalMethod, newMethod.method);
}
}
- hostClass.setDirectMethods(newMethods);
+ hostClass.appendDirectMethods(newMethods);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index e53f959..bc81538 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -96,7 +96,7 @@
// Initialize top-level naming state.
topLevelState = new Namespace(
getPackageBinaryNameFromJavaType(options.getProguardConfiguration().getPackagePrefix()));
- states.computeIfAbsent("", k -> topLevelState);
+ states.put("", topLevelState);
}
static class ClassRenaming {
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index bbb3391..be339b6 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.function.Function;
@@ -26,14 +27,14 @@
Function<DexType, ?> getKeyTransform() {
if (overloadAggressively) {
// Use the type as the key, hence reuse names per type.
- return a -> a;
+ return Function.identity();
} else {
// Always use the same key, hence do not reuse names per type.
return a -> Void.class;
}
}
- Map<DexField, DexString> computeRenaming(Timing timing) {
+ FieldRenaming computeRenaming(Timing timing) {
// Reserve names in all classes first. We do this in subtyping order so we do not
// shadow a reserved field in subclasses. While there is no concept of virtual field
// dispatch in Java, field resolution still traverses the super type chain and external
@@ -55,7 +56,20 @@
timing.begin("rename-references");
renameNonReboundReferences();
timing.end();
- return renaming;
+ return new FieldRenaming(renaming);
+ }
+
+ static class FieldRenaming {
+
+ final Map<DexField, DexString> renaming;
+
+ private FieldRenaming(Map<DexField, DexString> renaming) {
+ this.renaming = renaming;
+ }
+
+ public static FieldRenaming empty() {
+ return new FieldRenaming(ImmutableMap.of());
+ }
}
private void reserveNamesInSubtypes(DexType type, NamingState<DexType, ?> state) {
@@ -83,7 +97,7 @@
if (clazz == null) {
return;
}
- NamingState<DexType, ?> state = getState(clazz.type);
+ NamingState<DexType, ?> state = minifierState.getState(clazz.type);
assert state != null;
clazz.forEachField(field -> renameField(field, state));
type.forAllExtendsSubtypes(this::renameFieldsInSubtypes);
@@ -92,9 +106,7 @@
private void renameField(DexEncodedField encodedField, NamingState<DexType, ?> state) {
DexField field = encodedField.field;
if (!state.isReserved(field.name, field.type)) {
- renaming.put(
- field,
- state.assignNewNameFor(field.name, field.type, useUniqueMemberNames));
+ renaming.put(field, state.assignNewNameFor(field.name, field.type, useUniqueMemberNames));
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
new file mode 100644
index 0000000..b643be3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -0,0 +1,400 @@
+// 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;
+
+import static com.android.tools.r8.naming.MethodNameMinifier.shuffleMethods;
+
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MethodNameMinifier.FrontierState;
+import com.android.tools.r8.naming.MethodNameMinifier.MethodNamingState;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class InterfaceMethodNameMinifier {
+
+ private final AppInfoWithLiveness appInfo;
+ private final Set<DexCallSite> desugaredCallSites;
+ private final Equivalence<DexMethod> equivalence;
+ private final FrontierState frontierState;
+ private final MemberNameMinifier<DexMethod, DexProto>.State minifierState;
+ private final InternalOptions options;
+
+ private final Map<DexCallSite, DexString> callSiteRenamings = new IdentityHashMap<>();
+
+ /** A map from DexMethods to all the states linked to interfaces they appear in. */
+ private final Map<Wrapper<DexMethod>, Set<NamingState<DexProto, ?>>> globalStateMap =
+ new HashMap<>();
+
+ /** A map from DexMethods to the first interface state it was seen in. Used to pick good names. */
+ private final Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates = new HashMap<>();
+
+ /**
+ * A map from DexMethods to all the definitions seen. Needed as the Wrapper equalizes them all.
+ */
+ private final Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap = new HashMap<>();
+
+ InterfaceMethodNameMinifier(
+ AppInfoWithLiveness appInfo,
+ Set<DexCallSite> desugaredCallSites,
+ Equivalence<DexMethod> equivalence,
+ FrontierState frontierState,
+ MemberNameMinifier<DexMethod, DexProto>.State minifierState,
+ InternalOptions options) {
+ this.appInfo = appInfo;
+ this.desugaredCallSites = desugaredCallSites;
+ this.equivalence = equivalence;
+ this.frontierState = frontierState;
+ this.minifierState = minifierState;
+ this.options = options;
+ }
+
+ public Comparator<Wrapper<DexMethod>> createDefaultInterfaceMethodOrdering() {
+ return (a, b) -> globalStateMap.get(b).size() - globalStateMap.get(a).size();
+ }
+
+ Map<DexCallSite, DexString> getCallSiteRenamings() {
+ return callSiteRenamings;
+ }
+
+ private void reserveNamesInInterfaces() {
+ for (DexType type : DexType.allInterfaces(appInfo.dexItemFactory)) {
+ assert type.isInterface();
+ frontierState.allocateNamingStateAndReserve(type, type, null);
+ }
+ }
+
+ void assignNamesToInterfaceMethods(Timing timing) {
+ // Reserve all the names that are required for interfaces.
+ reserveNamesInInterfaces();
+
+ // First compute a map from method signatures to a set of naming states for interfaces and
+ // frontier states of classes that implement them. We add the frontier states so that we can
+ // reserve the names for later method naming.
+ timing.begin("Compute map");
+ for (DexType type : DexType.allInterfaces(appInfo.dexItemFactory)) {
+ assert type.isInterface();
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz != null) {
+ assert clazz.isInterface();
+ Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(type);
+ for (DexEncodedMethod method : shuffleMethods(clazz.methods(), options)) {
+ addStatesToGlobalMapForMethod(method.method, collectedStates, type);
+ }
+ }
+ }
+
+ // Collect the live call sites for multi-interface lambda expression renaming. For code with
+ // desugared lambdas this is a conservative estimate, as we don't track if the generated
+ // lambda classes survive into the output. As multi-interface lambda expressions are rare
+ // this is not a big deal.
+ Set<DexCallSite> liveCallSites = Sets.union(desugaredCallSites, appInfo.callSites);
+ // If the input program contains a multi-interface lambda expression that implements
+ // interface methods with different protos, we need to make sure that
+ // the implemented lambda methods are renamed to the same name.
+ // To achieve this, we map each DexCallSite that corresponds to a lambda expression to one of
+ // the DexMethods it implements, and we unify the DexMethods that need to be renamed together.
+ Map<DexCallSite, DexMethod> callSites = new IdentityHashMap<>();
+ // Union-find structure to keep track of methods that must be renamed together.
+ // Note that if the input does not use multi-interface lambdas,
+ // unificationParent will remain empty.
+ Map<Wrapper<DexMethod>, Wrapper<DexMethod>> unificationParent = new HashMap<>();
+ liveCallSites.forEach(
+ callSite -> {
+ Set<Wrapper<DexMethod>> callSiteMethods = new HashSet<>();
+ // Don't report errors, as the set of call sites is a conservative estimate, and can
+ // refer to interfaces which has been removed.
+ Set<DexEncodedMethod> implementedMethods =
+ appInfo.lookupLambdaImplementedMethods(callSite);
+ if (implementedMethods.isEmpty()) {
+ return;
+ }
+ callSites.put(callSite, implementedMethods.iterator().next().method);
+ for (DexEncodedMethod method : implementedMethods) {
+ DexType iface = method.method.holder;
+ assert iface.isInterface();
+ Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface);
+ addStatesToGlobalMapForMethod(method.method, collectedStates, iface);
+ callSiteMethods.add(equivalence.wrap(method.method));
+ }
+ if (callSiteMethods.size() > 1) {
+ // Implemented interfaces have different return types. Unify them.
+ Wrapper<DexMethod> mainKey = callSiteMethods.iterator().next();
+ mainKey = unificationParent.getOrDefault(mainKey, mainKey);
+ for (Wrapper<DexMethod> key : callSiteMethods) {
+ unificationParent.put(key, mainKey);
+ }
+ }
+ });
+ Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification = new HashMap<>();
+ for (Wrapper<DexMethod> key : unificationParent.keySet()) {
+ // Find root with path-compression.
+ Wrapper<DexMethod> root = unificationParent.get(key);
+ while (unificationParent.get(root) != root) {
+ Wrapper<DexMethod> k = unificationParent.get(unificationParent.get(root));
+ unificationParent.put(root, k);
+ root = k;
+ }
+ unification.computeIfAbsent(root, k -> new HashSet<>()).add(key);
+ }
+ timing.end();
+ // Go over every method and assign a name.
+ timing.begin("Allocate names");
+
+ // Sort the methods by the number of dependent states, so that we use short names for methods
+ // that are referenced in many places.
+ List<Wrapper<DexMethod>> interfaceMethods =
+ globalStateMap.keySet().stream()
+ .filter(wrapper -> unificationParent.getOrDefault(wrapper, wrapper).equals(wrapper))
+ .sorted(options.testing.minifier.createInterfaceMethodOrdering(this))
+ .collect(Collectors.toList());
+
+ // Propagate reserved names to all states.
+ List<Wrapper<DexMethod>> reservedInterfaceMethods =
+ interfaceMethods.stream()
+ .filter(wrapper -> anyIsReserved(wrapper, unification))
+ .collect(Collectors.toList());
+ for (Wrapper<DexMethod> key : reservedInterfaceMethods) {
+ propagateReservedNames(key, unification);
+ }
+
+ // Verify that there is no more to propagate.
+ assert reservedInterfaceMethods.stream()
+ .noneMatch(key -> propagateReservedNames(key, unification));
+
+ // Assign names to unreserved interface methods.
+ for (Wrapper<DexMethod> key : interfaceMethods) {
+ if (!reservedInterfaceMethods.contains(key)) {
+ assignNameToInterfaceMethod(key, unification);
+ }
+ }
+
+ for (Entry<DexCallSite, DexMethod> entry : callSites.entrySet()) {
+ DexMethod method = entry.getValue();
+ DexString renamed = minifierState.getRenaming(method);
+ if (originStates.get(equivalence.wrap(method)).isReserved(method.name, method.proto)) {
+ assert renamed == null;
+ callSiteRenamings.put(entry.getKey(), method.name);
+ } else {
+ assert renamed != null;
+ callSiteRenamings.put(entry.getKey(), renamed);
+ }
+ }
+ timing.end();
+ }
+
+ private boolean propagateReservedNames(
+ Wrapper<DexMethod> key, Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification) {
+ Set<Wrapper<DexMethod>> unifiedMethods =
+ unification.getOrDefault(key, Collections.singleton(key));
+ boolean changed = false;
+ for (Wrapper<DexMethod> wrapper : unifiedMethods) {
+ DexMethod unifiedMethod = wrapper.get();
+ assert unifiedMethod != null;
+ for (NamingState<DexProto, ?> namingState : globalStateMap.get(wrapper)) {
+ if (!namingState.isReserved(unifiedMethod.name, unifiedMethod.proto)) {
+ namingState.reserveName(unifiedMethod.name, unifiedMethod.proto);
+ changed = true;
+ }
+ }
+ }
+ return changed;
+ }
+
+ private void assignNameToInterfaceMethod(
+ Wrapper<DexMethod> key, Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification) {
+ List<MethodNamingState> collectedStates = new ArrayList<>();
+ Set<DexMethod> sourceMethods = Sets.newIdentityHashSet();
+ for (Wrapper<DexMethod> k : unification.getOrDefault(key, Collections.singleton(key))) {
+ DexMethod unifiedMethod = k.get();
+ assert unifiedMethod != null;
+ sourceMethods.addAll(sourceMethodsMap.get(k));
+ for (NamingState<DexProto, ?> namingState : globalStateMap.get(k)) {
+ collectedStates.add(
+ new MethodNamingState(namingState, unifiedMethod.name, unifiedMethod.proto));
+ }
+ }
+
+ DexMethod method = key.get();
+ assert method != null;
+
+ Set<String> loggingFilter = options.extensiveInterfaceMethodMinifierLoggingFilter;
+ if (!loggingFilter.isEmpty()) {
+ if (sourceMethods.stream().map(DexMethod::toSourceString).anyMatch(loggingFilter::contains)) {
+ print(method, sourceMethods, collectedStates, System.out);
+ }
+ }
+
+ MethodNamingState originState =
+ new MethodNamingState(originStates.get(key), method.name, method.proto);
+ assignNameForInterfaceMethodInAllStates(collectedStates, sourceMethods, originState);
+ }
+
+ private void assignNameForInterfaceMethodInAllStates(
+ List<MethodNamingState> collectedStates,
+ Set<DexMethod> sourceMethods,
+ MethodNamingState originState) {
+ assert !anyIsReserved(collectedStates);
+
+ // We use the origin state to allocate a name here so that we can reuse names between different
+ // unrelated interfaces. This saves some space. The alternative would be to use a global state
+ // for allocating names, which would save the work to search here.
+ DexString previousCandidate = null;
+ DexString candidate;
+ do {
+ candidate = originState.assignNewName();
+
+ // If the state returns the same candidate for two consecutive trials, it should be this case:
+ // 1) an interface method with the same signature (name, param) but different return type
+ // has been already renamed; and 2) -useuniqueclassmembernames is set.
+ // The option forces the naming state to return the same renamed name for the same signature.
+ // So, here we break the loop in an ad-hoc manner.
+ if (candidate != null && candidate == previousCandidate) {
+ assert minifierState.useUniqueMemberNames();
+ break;
+ }
+ for (MethodNamingState state : collectedStates) {
+ if (!state.isAvailable(candidate)) {
+ previousCandidate = candidate;
+ candidate = null;
+ break;
+ }
+ }
+ } while (candidate == null);
+ for (MethodNamingState state : collectedStates) {
+ state.addRenaming(candidate);
+ }
+ // Rename all methods in interfaces that gave rise to this renaming.
+ for (DexMethod sourceMethod : sourceMethods) {
+ minifierState.putRenaming(sourceMethod, candidate);
+ }
+ }
+
+ private void addStatesToGlobalMapForMethod(
+ DexMethod method, Set<NamingState<DexProto, ?>> collectedStates, DexType originInterface) {
+ Wrapper<DexMethod> key = equivalence.wrap(method);
+ globalStateMap.computeIfAbsent(key, k -> new HashSet<>()).addAll(collectedStates);
+ sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method);
+ originStates.putIfAbsent(key, minifierState.getState(originInterface));
+ }
+
+ private boolean anyIsReserved(
+ Wrapper<DexMethod> key, Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification) {
+ Set<Wrapper<DexMethod>> unifiedMethods =
+ unification.getOrDefault(key, Collections.singleton(key));
+ for (Wrapper<DexMethod> wrapper : unifiedMethods) {
+ DexMethod unifiedMethod = wrapper.get();
+ assert unifiedMethod != null;
+
+ for (NamingState<DexProto, ?> namingState : globalStateMap.get(wrapper)) {
+ if (namingState.isReserved(unifiedMethod.name, unifiedMethod.proto)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean anyIsReserved(List<MethodNamingState> collectedStates) {
+ DexString name = collectedStates.get(0).getName();
+ Map<DexProto, Boolean> globalStateCache = new IdentityHashMap<>();
+ for (MethodNamingState state : collectedStates) {
+ assert state.getName() == name;
+ boolean isReservedInGlobalState =
+ globalStateCache.computeIfAbsent(
+ state.getProto(), proto -> minifierState.isReservedInGlobalState(name, proto));
+ // TODO(christofferqa): Should this be using logical OR instead?
+ if (isReservedInGlobalState && state.isReserved()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Set<NamingState<DexProto, ?>> getReachableStates(DexType type) {
+ if (minifierState.useUniqueMemberNames()) {
+ return ImmutableSet.of(minifierState.globalState());
+ }
+
+ Set<DexType> reachableInterfaces = Sets.newIdentityHashSet();
+ reachableInterfaces.add(type);
+ collectSuperInterfaces(type, reachableInterfaces);
+ collectSubInterfaces(type, reachableInterfaces);
+
+ Set<NamingState<DexProto, ?>> reachableStates = new HashSet<>();
+ for (DexType reachableInterface : reachableInterfaces) {
+ // Add the interface itself.
+ reachableStates.add(minifierState.getState(reachableInterface));
+
+ // And the frontiers that correspond to the classes that implement the interface.
+ for (DexType frontier : reachableInterface.allImplementsSubtypes()) {
+ NamingState<DexProto, ?> state = minifierState.getState(frontierState.get(frontier));
+ assert state != null;
+ reachableStates.add(state);
+ }
+ }
+ return reachableStates;
+ }
+
+ private void collectSuperInterfaces(DexType iface, Set<DexType> interfaces) {
+ DexClass clazz = appInfo.definitionFor(iface);
+ // In cases where we lack the interface's definition, we can at least look at subtypes and
+ // tie those up to get proper naming.
+ if (clazz != null) {
+ for (DexType type : clazz.interfaces.values) {
+ if (interfaces.add(type)) {
+ collectSuperInterfaces(type, interfaces);
+ }
+ }
+ }
+ }
+
+ private void collectSubInterfaces(DexType iface, Set<DexType> interfaces) {
+ for (DexType subtype : iface.allExtendsSubtypes()) {
+ assert subtype.isInterface();
+ if (interfaces.add(subtype)) {
+ collectSubInterfaces(subtype, interfaces);
+ }
+ }
+ }
+
+ private void print(
+ DexMethod method,
+ Set<DexMethod> sourceMethods,
+ List<MethodNamingState> collectedStates,
+ PrintStream out) {
+ out.println("-----------------------------------------------------------------------");
+ out.println("assignNameToInterfaceMethod(`" + method.toSourceString() + "`)");
+ out.println("-----------------------------------------------------------------------");
+ out.println("Source methods:");
+ for (DexMethod sourceMethod : sourceMethods) {
+ out.println(" " + sourceMethod.toSourceString());
+ }
+ out.println("States:");
+ collectedStates.forEach(state -> state.print(" ", minifierState::getStateKey, out));
+ out.println();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 7f28744..419bfbb 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -22,11 +24,16 @@
protected final List<String> dictionary;
protected final Map<MemberType, DexString> renaming = new IdentityHashMap<>();
- protected final Map<DexType, NamingState<StateType, ?>> states = new IdentityHashMap<>();
protected final NamingState<StateType, ?> globalState;
protected final boolean useUniqueMemberNames;
protected final boolean overloadAggressively;
+ protected final State minifierState = new State();
+
+ // The use of a bidirectional map allows us to map a naming state to the type it represents,
+ // which is useful for debugging.
+ private final BiMap<DexType, NamingState<StateType, ?>> states = HashBiMap.create();
+
MemberNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, InternalOptions options) {
this.appInfo = appInfo;
this.rootSet = rootSet;
@@ -46,7 +53,36 @@
return useUniqueMemberNames ? globalState : states.computeIfAbsent(type, f);
}
- protected NamingState<StateType, ?> getState(DexType type) {
- return useUniqueMemberNames ? globalState : states.get(type);
+ // A class that provides access to the minification state. An instance of this class is passed
+ // from the method name minifier to the interface method name minifier.
+ class State {
+
+ DexString getRenaming(MemberType key) {
+ return renaming.get(key);
+ }
+
+ void putRenaming(MemberType key, DexString value) {
+ renaming.put(key, value);
+ }
+
+ NamingState<StateType, ?> getState(DexType type) {
+ return useUniqueMemberNames ? globalState : states.get(type);
+ }
+
+ DexType getStateKey(NamingState<StateType, ?> state) {
+ return states.inverse().get(state);
+ }
+
+ NamingState<StateType, ?> globalState() {
+ return globalState;
+ }
+
+ boolean isReservedInGlobalState(DexString name, StateType state) {
+ return globalState.isReserved(name, state);
+ }
+
+ boolean useUniqueMemberNames() {
+ return useUniqueMemberNames;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 5f990ac..8864271 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -18,15 +18,11 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collections;
+import com.google.common.collect.ImmutableMap;
+import java.io.PrintStream;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
@@ -91,18 +87,15 @@
*/
class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> {
- private final Set<DexCallSite> desugaredCallSites;
-
private final Equivalence<DexMethod> equivalence;
- private final Map<DexCallSite, DexString> callSiteRenaming = new IdentityHashMap<>();
+
+ private final FrontierState frontierState = new FrontierState();
MethodNameMinifier(
AppInfoWithLiveness appInfo,
RootSet rootSet,
- Set<DexCallSite> desugaredCallSites,
InternalOptions options) {
super(appInfo, rootSet, options);
- this.desugaredCallSites = desugaredCallSites;
equivalence =
overloadAggressively
? MethodSignatureEquivalence.get()
@@ -121,6 +114,7 @@
}
static class MethodRenaming {
+
final Map<DexMethod, DexString> renaming;
final Map<DexCallSite, DexString> callSiteRenaming;
@@ -129,37 +123,37 @@
this.renaming = renaming;
this.callSiteRenaming = callSiteRenaming;
}
+
+ public static MethodRenaming empty() {
+ return new MethodRenaming(ImmutableMap.of(), ImmutableMap.of());
+ }
}
- MethodRenaming computeRenaming(Timing timing) {
+ MethodRenaming computeRenaming(Set<DexCallSite> desugaredCallSites, Timing timing) {
// Phase 1: Reserve all the names that need to be kept and allocate linked state in the
// library part.
timing.begin("Phase 1");
- Map<DexType, DexType> frontierMap = new IdentityHashMap<>();
- reserveNamesInClasses(appInfo.dexItemFactory.objectType,
- appInfo.dexItemFactory.objectType,
- null, frontierMap);
+ reserveNamesInClasses();
timing.end();
- // Phase 2: Reserve all the names that are required for interfaces.
+ // Phase 2: Reserve all the names that are required for interfaces, and then assign names to
+ // interface methods. These are assigned by finding a name that is free in all naming
+ // states that may hold an implementation
timing.begin("Phase 2");
- DexType.forAllInterfaces(
- appInfo.dexItemFactory, iface -> reserveNamesInInterfaces(iface, frontierMap));
+ InterfaceMethodNameMinifier interfaceMethodNameMinifier =
+ new InterfaceMethodNameMinifier(
+ appInfo, desugaredCallSites, equivalence, frontierState, minifierState, options);
+ interfaceMethodNameMinifier.assignNamesToInterfaceMethods(timing);
timing.end();
- // Phase 3: Assign names to interface methods. These are assigned by finding a name that is
- // free in all naming states that may hold an implementation.
+ // Phase 3: Assign names top-down by traversing the subtype hierarchy.
timing.begin("Phase 3");
- assignNamesToInterfaceMethods(frontierMap, timing);
- timing.end();
- // Phase 4: Assign names top-down by traversing the subtype hierarchy.
- timing.begin("Phase 4");
assignNamesToClassesMethods(appInfo.dexItemFactory.objectType, false);
timing.end();
// Phase 4: Do the same for private methods.
- timing.begin("Phase 5");
+ timing.begin("Phase 4");
assignNamesToClassesMethods(appInfo.dexItemFactory.objectType, true);
timing.end();
- return new MethodRenaming(renaming, callSiteRenaming);
+ return new MethodRenaming(renaming, interfaceMethodNameMinifier.getCallSiteRenamings());
}
private void assignNamesToClassesMethods(DexType type, boolean doPrivates) {
@@ -167,7 +161,7 @@
if (holder != null && !holder.isLibraryClass()) {
Map<Wrapper<DexMethod>, DexString> renamingAtThisLevel = new HashMap<>();
NamingState<DexProto, ?> state =
- computeStateIfAbsent(type, k -> getState(holder.superType).createChild());
+ computeStateIfAbsent(type, k -> minifierState.getState(holder.superType).createChild());
for (DexEncodedMethod method : holder.allMethodsSorted()) {
assignNameToMethod(method, state, renamingAtThisLevel, doPrivates);
}
@@ -201,291 +195,77 @@
}
}
- private Set<NamingState<DexProto, ?>> getReachableStates(DexType type,
- Map<DexType, DexType> frontierMap) {
- Set<DexType> interfaces = Sets.newIdentityHashSet();
- interfaces.add(type);
- collectSuperInterfaces(type, interfaces);
- collectSubInterfaces(type, interfaces);
- Set<NamingState<DexProto, ?>> reachableStates = new HashSet<>();
- for (DexType iface : interfaces) {
- // Add the interface itself
- reachableStates.add(getState(iface));
- // And the frontiers that correspond to the classes that implement the interface.
- iface.forAllImplementsSubtypes(t -> {
- NamingState<DexProto, ?> state = getState(frontierMap.get(t));
- assert state != null;
- reachableStates.add(state);
- });
- }
- return reachableStates;
+ private void reserveNamesInClasses() {
+ reserveNamesInClasses(
+ appInfo.dexItemFactory.objectType, appInfo.dexItemFactory.objectType, null);
}
- private void assignNamesToInterfaceMethods(Map<DexType, DexType> frontierMap, Timing timing) {
- // First compute a map from method signatures to a set of naming states for interfaces and
- // frontier states of classes that implement them. We add the frontier states so that we can
- // reserve the names for later method naming.
- timing.begin("Compute map");
- // A map from DexMethods to all the states linked to interfaces they appear in.
- Map<Wrapper<DexMethod>, Set<NamingState<DexProto, ?>>> globalStateMap = new HashMap<>();
- // A map from DexMethods to all the definitions seen. Needed as the Wrapper equalizes them all.
- Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap = new HashMap<>();
- // A map from DexMethods to the first interface state it was seen in. Used to pick good names.
- Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates = new HashMap<>();
- DexType.forAllInterfaces(appInfo.dexItemFactory, iface -> {
- assert iface.isInterface();
- DexClass clazz = appInfo.definitionFor(iface);
- if (clazz != null) {
- Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface, frontierMap);
- for (DexEncodedMethod method : shuffleMethods(clazz.methods())) {
- addStatesToGlobalMapForMethod(
- method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface);
- }
- }
- });
- // Collect the live call sites for multi-interface lambda expression renaming. For code with
- // desugared lambdas this is a conservative estimate, as we don't track if the generated
- // lambda classes survive into the output. As multi-interface lambda expressions are rare
- // this is not a big deal.
- Set<DexCallSite> liveCallSites = Sets.union(desugaredCallSites, appInfo.callSites);
- // If the input program contains a multi-interface lambda expression that implements
- // interface methods with different protos, we need to make sure that
- // the implemented lambda methods are renamed to the same name.
- // To achieve this, we map each DexCallSite that corresponds to a lambda expression to one of
- // the DexMethods it implements, and we unify the DexMethods that need to be renamed together.
- Map<DexCallSite, DexMethod> callSites = new IdentityHashMap<>();
- // Union-find structure to keep track of methods that must be renamed together.
- // Note that if the input does not use multi-interface lambdas,
- // unificationParent will remain empty.
- Map<Wrapper<DexMethod>, Wrapper<DexMethod>> unificationParent = new HashMap<>();
- liveCallSites.forEach(
- callSite -> {
- Set<Wrapper<DexMethod>> callSiteMethods = new HashSet<>();
- // Don't report errors, as the set of call sites is a conservative estimate, and can
- // refer to interfaces which has been removed.
- Set<DexEncodedMethod> implementedMethods =
- appInfo.lookupLambdaImplementedMethods(callSite);
- if (implementedMethods.isEmpty()) {
- return;
- }
- callSites.put(callSite, implementedMethods.iterator().next().method);
- for (DexEncodedMethod method : implementedMethods) {
- DexType iface = method.method.holder;
- assert iface.isInterface();
- Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface, frontierMap);
- addStatesToGlobalMapForMethod(
- method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface);
- callSiteMethods.add(equivalence.wrap(method.method));
- }
- if (callSiteMethods.size() > 1) {
- // Implemented interfaces have different return types. Unify them.
- Wrapper<DexMethod> mainKey = callSiteMethods.iterator().next();
- mainKey = unificationParent.getOrDefault(mainKey, mainKey);
- for (Wrapper<DexMethod> key : callSiteMethods) {
- unificationParent.put(key, mainKey);
- }
- }
- });
- Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification = new HashMap<>();
- for (Wrapper<DexMethod> key : unificationParent.keySet()) {
- // Find root with path-compression.
- Wrapper<DexMethod> root = unificationParent.get(key);
- while (unificationParent.get(root) != root) {
- Wrapper<DexMethod> k = unificationParent.get(unificationParent.get(root));
- unificationParent.put(root, k);
- root = k;
- }
- unification.computeIfAbsent(root, k -> new HashSet<>()).add(key);
- }
- timing.end();
- // Go over every method and assign a name.
- timing.begin("Allocate names");
- // Sort the methods by the number of dependent states, so that we use short names for methods
- // references in many places.
- List<Wrapper<DexMethod>> methods = new ArrayList<>(globalStateMap.keySet());
- methods.sort((a, b) -> globalStateMap.get(b).size() - globalStateMap.get(a).size());
- for (Wrapper<DexMethod> key : methods) {
- if (!unificationParent.getOrDefault(key, key).equals(key)) {
- continue;
- }
- List<MethodNamingState> collectedStates = new ArrayList<>();
- Set<DexMethod> sourceMethods = Sets.newIdentityHashSet();
- for (Wrapper<DexMethod> k : unification.getOrDefault(key, Collections.singleton(key))) {
- DexMethod unifiedMethod = k.get();
- assert unifiedMethod != null;
- sourceMethods.addAll(sourceMethodsMap.get(k));
- for (NamingState<DexProto, ?> namingState : globalStateMap.get(k)) {
- collectedStates.add(
- new MethodNamingState(namingState, unifiedMethod.name, unifiedMethod.proto));
- }
- }
- DexMethod method = key.get();
- assert method != null;
- MethodNamingState originState =
- new MethodNamingState(originStates.get(key), method.name, method.proto);
- assignNameForInterfaceMethodInAllStates(collectedStates, sourceMethods, originState);
- }
- for (Entry<DexCallSite, DexMethod> entry : callSites.entrySet()) {
- DexMethod method = entry.getValue();
- DexString renamed = renaming.get(method);
- if (originStates.get(equivalence.wrap(method)).isReserved(method.name, method.proto)) {
- assert renamed == null;
- callSiteRenaming.put(entry.getKey(), method.name);
- } else {
- assert renamed != null;
- callSiteRenaming.put(entry.getKey(), renamed);
- }
- }
- timing.end();
- }
-
- private void collectSuperInterfaces(DexType iface, Set<DexType> interfaces) {
- DexClass clazz = appInfo.definitionFor(iface);
- // In cases where we lack the interface's definition, we can at least look at subtypes and
- // tie those up to get proper naming.
- if (clazz != null) {
- for (DexType type : clazz.interfaces.values) {
- if (interfaces.add(type)) {
- collectSuperInterfaces(type, interfaces);
- }
- }
- }
- }
-
- private void collectSubInterfaces(DexType iface, Set<DexType> interfaces) {
- iface.forAllExtendsSubtypes(subtype -> {
- assert subtype.isInterface();
- if (interfaces.add(subtype)) {
- collectSubInterfaces(subtype, interfaces);
- }
- });
- }
-
- private void addStatesToGlobalMapForMethod(
- DexEncodedMethod method,
- Set<NamingState<DexProto, ?>> collectedStates,
- Map<Wrapper<DexMethod>, Set<NamingState<DexProto, ?>>> globalStateMap,
- Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap,
- Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates,
- DexType originInterface) {
- Wrapper<DexMethod> key = equivalence.wrap(method.method);
- Set<NamingState<DexProto, ?>> stateSet =
- globalStateMap.computeIfAbsent(key, k -> new HashSet<>());
- stateSet.addAll(collectedStates);
- sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method.method);
- originStates.putIfAbsent(key, getState(originInterface));
- }
-
- private void assignNameForInterfaceMethodInAllStates(
- List<MethodNamingState> collectedStates,
- Set<DexMethod> sourceMethods,
- MethodNamingState originState) {
- if (anyIsReserved(collectedStates)) {
- // This method's name is reserved in at least one naming state, so reserve it everywhere.
- for (MethodNamingState state : collectedStates) {
- state.reserveName();
- }
- return;
- }
- // We use the origin state to allocate a name here so that we can reuse names between different
- // unrelated interfaces. This saves some space. The alternative would be to use a global state
- // for allocating names, which would save the work to search here.
- DexString previousCandidate = null;
- DexString candidate;
- do {
- candidate = originState.assignNewNameFor(false);
- // If the state returns the same candidate for two consecutive trials, it should be this case:
- // 1) an interface method with the same signature (name, param) but different return type
- // has been already renamed; and 2) -useuniqueclassmembernames is set.
- // The option forces the naming state to return the same renamed name for the same signature.
- // So, here we break the loop in an ad-hoc manner.
- if (candidate != null && candidate == previousCandidate) {
- assert useUniqueMemberNames;
- break;
- }
- for (MethodNamingState state : collectedStates) {
- if (!state.isAvailable(candidate)) {
- previousCandidate = candidate;
- candidate = null;
- break;
- }
- }
- } while (candidate == null);
- for (MethodNamingState state : collectedStates) {
- state.addRenaming(candidate);
- }
- // Rename all methods in interfaces that gave rise to this renaming.
- for (DexMethod sourceMethod : sourceMethods) {
- renaming.put(sourceMethod, candidate);
- }
- }
-
- private boolean anyIsReserved(List<MethodNamingState> collectedStates) {
- DexString name = collectedStates.get(0).getName();
- Map<DexProto, Boolean> globalStateCache = new HashMap<>();
- for (MethodNamingState state : collectedStates) {
- assert state.getName() == name;
- if (globalStateCache.computeIfAbsent(
- state.getProto(), proto -> globalState.isReserved(name, proto))
- && state.isReserved()) {
- return true;
- }
- }
- return false;
- }
-
- private void reserveNamesInClasses(DexType type, DexType libraryFrontier,
- NamingState<DexProto, ?> parent,
- Map<DexType, DexType> frontierMap) {
+ private void reserveNamesInClasses(
+ DexType type, DexType libraryFrontier, NamingState<DexProto, ?> parent) {
assert !type.isInterface();
- DexClass holder = appInfo.definitionFor(type);
- NamingState<DexProto, ?> state = allocateNamingStateAndReserve(holder, type, libraryFrontier,
- parent, frontierMap);
+ NamingState<DexProto, ?> state =
+ frontierState.allocateNamingStateAndReserve(type, libraryFrontier, parent);
+
// If this is a library class (or effectively a library class as it is missing) move the
// frontier forward.
- type.forAllExtendsSubtypes(subtype -> {
- assert !subtype.isInterface();
- reserveNamesInClasses(subtype,
- holder == null || holder.isLibraryClass() ? subtype : libraryFrontier,
- state, frontierMap);
- });
- }
-
- private void reserveNamesInInterfaces(DexType type, Map<DexType, DexType> frontierMap) {
- assert type.isInterface();
- frontierMap.put(type, type);
DexClass holder = appInfo.definitionFor(type);
- allocateNamingStateAndReserve(holder, type, type, null, frontierMap);
- }
-
- private NamingState<DexProto, ?> allocateNamingStateAndReserve(DexClass holder, DexType type,
- DexType libraryFrontier,
- NamingState<DexProto, ?> parent,
- Map<DexType, DexType> frontierMap) {
- frontierMap.put(type, libraryFrontier);
- NamingState<DexProto, ?> state =
- computeStateIfAbsent(
- libraryFrontier,
- ignore -> parent == null
- ? NamingState.createRoot(
- appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames)
- : parent.createChild());
- if (holder != null) {
- boolean keepAll = holder.isLibraryClass() || holder.accessFlags.isAnnotation();
- for (DexEncodedMethod method : shuffleMethods(holder.methods())) {
- reserveNamesForMethod(method, keepAll, state);
- }
+ for (DexType subtype : type.allExtendsSubtypes()) {
+ assert !subtype.isInterface();
+ reserveNamesInClasses(
+ subtype, holder == null || holder.isLibraryClass() ? subtype : libraryFrontier, state);
}
- return state;
}
- private void reserveNamesForMethod(
- DexEncodedMethod encodedMethod, boolean keepAll, NamingState<DexProto, ?> state) {
- DexMethod method = encodedMethod.method;
- if (keepAll || rootSet.noObfuscation.contains(method)) {
+ class FrontierState {
+
+ private final Map<DexType, DexType> frontiers = new IdentityHashMap<>();
+
+ NamingState<DexProto, ?> allocateNamingStateAndReserve(
+ DexType type, DexType frontier, NamingState<DexProto, ?> parent) {
+ if (frontier != type) {
+ frontiers.put(type, frontier);
+ }
+
+ NamingState<DexProto, ?> state =
+ computeStateIfAbsent(
+ frontier,
+ ignore ->
+ parent == null
+ ? NamingState.createRoot(
+ appInfo.dexItemFactory,
+ dictionary,
+ getKeyTransform(),
+ useUniqueMemberNames)
+ : parent.createChild());
+
+ DexClass holder = appInfo.definitionFor(type);
+ if (holder != null) {
+ boolean keepAll = holder.isLibraryClass() || holder.accessFlags.isAnnotation();
+ for (DexEncodedMethod method : shuffleMethods(holder.methods(), options)) {
+ // TODO(christofferqa): Wouldn't it be sufficient only to reserve names for non-private
+ // methods?
+ if (keepAll || rootSet.noObfuscation.contains(method.method)) {
+ reserveNamesForMethod(method.method, state);
+ }
+ }
+ }
+
+ return state;
+ }
+
+ private void reserveNamesForMethod(DexMethod method, NamingState<DexProto, ?> state) {
state.reserveName(method.name, method.proto);
globalState.reserveName(method.name, method.proto);
}
+
+ public DexType get(DexType type) {
+ return frontiers.getOrDefault(type, type);
+ }
+
+ public DexType put(DexType type, DexType frontier) {
+ assert frontier != type;
+ return frontiers.put(type, frontier);
+ }
}
/**
@@ -493,7 +273,7 @@
* simply defers to parent.METHOD(name, proto, ...). This allows R8 to assign the same name to
* methods with different prototypes, which is needed for multi-interface lambdas.
*/
- private static class MethodNamingState {
+ static class MethodNamingState {
private final NamingState<DexProto, ?> parent;
private final DexString name;
@@ -506,8 +286,8 @@
this.proto = proto;
}
- DexString assignNewNameFor(boolean markAsUsed) {
- return parent.assignNewNameFor(name, proto, markAsUsed);
+ DexString assignNewName() {
+ return parent.assignNewNameFor(name, proto, false);
}
void reserveName() {
@@ -533,11 +313,25 @@
DexProto getProto() {
return proto;
}
+
+ void print(
+ String indentation,
+ Function<NamingState<DexProto, ?>, DexType> stateKeyGetter,
+ PrintStream out) {
+ DexType stateKey = stateKeyGetter.apply(parent);
+ out.print(indentation);
+ out.print(stateKey != null ? stateKey.toSourceString() : "<?>");
+ out.print(".");
+ out.print(name.toSourceString());
+ out.println(proto.toSmaliString());
+ parent.printState(proto, indentation + " ", out);
+ }
}
// Shuffles the given methods if assertions are enabled and deterministic debugging is disabled.
// Used to ensure that the generated output is deterministic.
- private Iterable<DexEncodedMethod> shuffleMethods(Iterable<DexEncodedMethod> methods) {
+ static Iterable<DexEncodedMethod> shuffleMethods(
+ Iterable<DexEncodedMethod> methods, InternalOptions options) {
return options.testing.irOrdering.order(methods);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
index 3ae9d06..e3d09b6 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
@@ -16,6 +16,7 @@
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
@@ -38,6 +39,12 @@
return copy;
}
+ private <T> List<T> sortedCopy(List<T> source, Comparator<? super T> comparator) {
+ List<T> copy = new ArrayList<>(source);
+ Collections.sort(copy, comparator);
+ return copy;
+ }
+
private void writeClass(DexProgramClass clazz, StringBuilder out) {
seenTypes.add(clazz.type);
DexString descriptor = namingLens.lookupDescriptor(clazz.type);
@@ -87,7 +94,7 @@
out.append(renamed).append(NEW_LINE);
}
- private void writeMethods(DexEncodedMethod[] methods, StringBuilder out) {
+ private void writeMethods(List<DexEncodedMethod> methods, StringBuilder out) {
for (DexEncodedMethod encodedMethod : methods) {
DexMethod method = encodedMethod.method;
DexString renamed = namingLens.lookupName(method);
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index e4ccbcd..5333125 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
+import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -56,21 +57,28 @@
ClassNameMinifier classNameMinifier = new ClassNameMinifier(appInfo, rootSet, options);
ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
timing.end();
+
+ assert new MinifiedRenaming(
+ classRenaming, MethodRenaming.empty(), FieldRenaming.empty(), appInfo)
+ .verifyNoCollisions(appInfo.classes(), appInfo.dexItemFactory);
+
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
- new MethodNameMinifier(appInfo, rootSet, desugaredCallSites, options)
- .computeRenaming(timing);
+ new MethodNameMinifier(appInfo, rootSet, options)
+ .computeRenaming(desugaredCallSites, timing);
timing.end();
+
+ assert new MinifiedRenaming(classRenaming, methodRenaming, FieldRenaming.empty(), appInfo)
+ .verifyNoCollisions(appInfo.classes(), appInfo.dexItemFactory);
+
timing.begin("MinifyFields");
- Map<DexField, DexString> fieldRenaming =
+ FieldRenaming fieldRenaming =
new FieldNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
timing.end();
- NamingLens lens =
- new MinifiedRenaming(
- classRenaming,
- methodRenaming,
- fieldRenaming,
- appInfo);
+
+ NamingLens lens = new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+ assert lens.verifyNoCollisions(appInfo.classes(), appInfo.dexItemFactory);
+
timing.begin("MinifyIdentifiers");
new IdentifierMinifier(
appInfo, options.getProguardConfiguration().getAdaptClassStrings(), lens).run();
@@ -87,14 +95,14 @@
private MinifiedRenaming(
ClassRenaming classRenaming,
MethodRenaming methodRenaming,
- Map<DexField, DexString> fieldRenaming,
+ FieldRenaming fieldRenaming,
AppInfo appInfo) {
this.appInfo = appInfo;
this.packageRenaming = classRenaming.packageRenaming;
renaming.putAll(classRenaming.classRenaming);
renaming.putAll(methodRenaming.renaming);
renaming.putAll(methodRenaming.callSiteRenaming);
- renaming.putAll(fieldRenaming);
+ renaming.putAll(fieldRenaming.renaming);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index cee867e..aecae1e 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -7,10 +7,14 @@
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -19,7 +23,10 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -70,6 +77,40 @@
return lookupName(reference.asDexField());
}
+ public final DexField lookupField(DexField field, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createField(
+ lookupType(field.clazz, dexItemFactory),
+ lookupType(field.type, dexItemFactory),
+ lookupName(field));
+ }
+
+ public final DexMethod lookupMethod(DexMethod method, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createMethod(
+ lookupType(method.holder, dexItemFactory),
+ lookupProto(method.proto, dexItemFactory),
+ lookupName(method));
+ }
+
+ private DexProto lookupProto(DexProto proto, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createProto(
+ lookupType(proto.returnType, dexItemFactory),
+ Arrays.stream(proto.parameters.values)
+ .map(type -> lookupType(type, dexItemFactory))
+ .toArray(DexType[]::new));
+ }
+
+ public final DexType lookupType(DexType type, DexItemFactory dexItemFactory) {
+ if (type.isPrimitiveType() || type.isVoidType()) {
+ return type;
+ }
+ if (type.isArrayType()) {
+ DexType newBaseType = lookupType(type.toBaseType(dexItemFactory), dexItemFactory);
+ return type.replaceBaseType(newBaseType, dexItemFactory);
+ }
+ assert type.isClassType();
+ return dexItemFactory.createType(lookupDescriptor(type));
+ }
+
public static NamingLens getIdentityLens() {
return new IdentityLens();
}
@@ -97,6 +138,34 @@
*/
public abstract boolean checkTargetCanBeTranslated(DexMethod item);
+ public final boolean verifyNoCollisions(
+ Iterable<DexProgramClass> classes, DexItemFactory dexItemFactory) {
+ Set<DexReference> references = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : classes) {
+ {
+ DexType newType = lookupType(clazz.type, dexItemFactory);
+ boolean referencesChanged = references.add(newType);
+ assert referencesChanged
+ : "Duplicate definition of type `" + newType.toSourceString() + "`";
+ }
+
+ for (DexEncodedField field : clazz.fields()) {
+ DexField newField = lookupField(field.field, dexItemFactory);
+ boolean referencesChanged = references.add(newField);
+ assert referencesChanged
+ : "Duplicate definition of field `" + newField.toSourceString() + "`";
+ }
+
+ for (DexEncodedMethod method : clazz.methods()) {
+ DexMethod newMethod = lookupMethod(method.method, dexItemFactory);
+ boolean referencesChanged = references.add(newMethod);
+ assert referencesChanged
+ : "Duplicate definition of method `" + newMethod.toSourceString() + "`";
+ }
+ }
+ return true;
+ }
+
private static class IdentityLens extends NamingLens {
private IdentityLens() {
diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/NamingState.java
index 999579a..d414b3b 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingState.java
@@ -11,10 +11,12 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
+import java.io.PrintStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
@@ -129,7 +131,19 @@
state.addRenaming(original, key, newName);
}
- private class InternalState {
+ void printState(ProtoType proto, String indentation, PrintStream out) {
+ KeyType key = keyTransform.apply(proto);
+ InternalState state = findInternalStateFor(key);
+ if (state != null) {
+ state.printReservedNames(indentation, out);
+ state.printRenamings(indentation, out);
+ } else {
+ out.print(indentation);
+ out.println("<NO STATE>");
+ }
+ }
+
+ class InternalState {
private static final int INITIAL_NAME_COUNT = 1;
private final char[] EMPTY_CHAR_ARRAY = new char[0];
@@ -229,5 +243,49 @@
return StringUtils.numberToIdentifier(EMPTY_CHAR_ARRAY, nameCount++, false);
}
}
+
+ void printReservedNames(String indentation, PrintStream out) {
+ out.print(indentation);
+ out.print("Reserved names:");
+ if (reservedNames == null || reservedNames.isEmpty()) {
+ out.print(" <NO RESERVED NAMES>");
+ } else {
+ for (DexString reservedName : reservedNames) {
+ out.print(System.lineSeparator());
+ out.print(indentation);
+ out.print(" ");
+ out.print(reservedName.toSourceString());
+ }
+ }
+ out.println();
+ if (parentInternalState != null) {
+ parentInternalState.printReservedNames(indentation + " ", out);
+ }
+ }
+
+ void printRenamings(String indentation, PrintStream out) {
+ out.print(indentation);
+ out.print("Renamings:");
+ if (renamings == null || renamings.isEmpty()) {
+ out.print(" <NO RENAMINGS>");
+ } else {
+ for (DexString original : renamings.rowKeySet()) {
+ Map<KeyType, DexString> row = renamings.row(original);
+ for (Entry<KeyType, DexString> entry : row.entrySet()) {
+ out.print(System.lineSeparator());
+ out.print(indentation);
+ out.print(" ");
+ out.print(original.toSourceString());
+ out.print(entry.getKey().toString());
+ out.print(" -> ");
+ out.print(entry.getValue().toSourceString());
+ }
+ }
+ }
+ out.println();
+ if (parentInternalState != null) {
+ parentInternalState.printRenamings(indentation + " ", out);
+ }
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index d5add45..b0e2f4c 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClass.MethodSetter;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -25,6 +26,7 @@
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
@@ -317,18 +319,18 @@
clazz.superType = substituteType(clazz.superType, null);
clazz.interfaces = substituteTypesIn(clazz.interfaces);
clazz.annotations = substituteTypesIn(clazz.annotations);
- clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
- clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+ fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
+ fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
}
- private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
+ private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
if (methods == null) {
- return null;
+ return;
}
- for (int i = 0; i < methods.length; i++) {
- DexEncodedMethod encodedMethod = methods[i];
+ for (int i = 0; i < methods.size(); i++) {
+ DexEncodedMethod encodedMethod = methods.get(i);
DexMethod appliedMethod = appliedLense.lookupMethod(encodedMethod.method);
DexType newHolderType = substituteType(appliedMethod.holder, encodedMethod);
DexProto newProto = substituteTypesIn(appliedMethod.proto, encodedMethod);
@@ -341,9 +343,8 @@
newMethod = appliedMethod;
}
// Explicitly fix members.
- methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
}
- return methods;
}
private DexEncodedField[] substituteTypesIn(DexEncodedField[] fields) {
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 59caf25..f2e6c6d 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.Sets;
-import java.util.Arrays;
+import java.util.List;
import java.util.Set;
public class VisibilityBridgeRemover {
@@ -24,11 +24,17 @@
}
private void removeUnneededVisibilityBridgesFromClass(DexProgramClass clazz) {
- clazz.setDirectMethods(removeUnneededVisibilityBridges(clazz.directMethods()));
- clazz.setVirtualMethods(removeUnneededVisibilityBridges(clazz.virtualMethods()));
+ DexEncodedMethod[] newDirectMethods = removeUnneededVisibilityBridges(clazz.directMethods());
+ if (newDirectMethods != null) {
+ clazz.setDirectMethods(newDirectMethods);
+ }
+ DexEncodedMethod[] newVirtualMethods = removeUnneededVisibilityBridges(clazz.virtualMethods());
+ if (newVirtualMethods != null) {
+ clazz.setVirtualMethods(newVirtualMethods);
+ }
}
- private DexEncodedMethod[] removeUnneededVisibilityBridges(DexEncodedMethod[] methods) {
+ private DexEncodedMethod[] removeUnneededVisibilityBridges(List<DexEncodedMethod> methods) {
Set<DexEncodedMethod> methodsToBeRemoved = null;
for (DexEncodedMethod method : methods) {
if (isUnneededVisibilityBridge(method)) {
@@ -40,11 +46,11 @@
}
if (methodsToBeRemoved != null) {
Set<DexEncodedMethod> finalMethodsToBeRemoved = methodsToBeRemoved;
- return Arrays.stream(methods)
+ return methods.stream()
.filter(method -> !finalMethodsToBeRemoved.contains(method))
.toArray(DexEncodedMethod[]::new);
}
- return methods;
+ return null;
}
private boolean isUnneededVisibilityBridge(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index ae88ba7..0bdbcee 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -38,20 +38,23 @@
DexClass holder = appInfo.definitionFor(type);
scope = scope.newNestedScope();
if (holder != null && !holder.isLibraryClass()) {
- holder.setVirtualMethods(processMethods(holder.virtualMethods()));
+ DexEncodedMethod[] newVirtualMethods = processMethods(holder.virtualMethods());
+ if (newVirtualMethods != null) {
+ holder.setVirtualMethods(newVirtualMethods);
+ }
}
type.forAllExtendsSubtypes(this::processClass);
scope = scope.getParent();
}
- private DexEncodedMethod[] processMethods(DexEncodedMethod[] virtualMethods) {
+ private DexEncodedMethod[] processMethods(List<DexEncodedMethod> virtualMethods) {
if (virtualMethods == null) {
return null;
}
// Removal of abstract methods is rare, so avoid copying the array until we find one.
List<DexEncodedMethod> methods = null;
- for (int i = 0; i < virtualMethods.length; i++) {
- DexEncodedMethod method = virtualMethods[i];
+ for (int i = 0; i < virtualMethods.size(); i++) {
+ DexEncodedMethod method = virtualMethods.get(i);
if (scope.addMethodIfMoreVisible(method)
|| !method.accessFlags.isAbstract()
|| appInfo.isPinned(method.method)) {
@@ -60,9 +63,9 @@
}
} else {
if (methods == null) {
- methods = new ArrayList<>(virtualMethods.length - 1);
+ methods = new ArrayList<>(virtualMethods.size() - 1);
for (int j = 0; j < i; j++) {
- methods.add(virtualMethods[j]);
+ methods.add(virtualMethods.get(j));
}
}
if (Log.ENABLED) {
@@ -70,7 +73,10 @@
}
}
}
- return methods == null ? virtualMethods : methods.toArray(new DexEncodedMethod[methods.size()]);
+ if (methods != null) {
+ return methods.toArray(new DexEncodedMethod[methods.size()]);
+ }
+ return null;
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 8ce5cb0..3fa9db5 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
import java.util.List;
public class AnnotationRemover {
@@ -184,8 +183,9 @@
return original;
}
assert definition.isInterface();
- boolean liveGetter = Arrays.stream(definition.virtualMethods())
- .anyMatch(method -> method.method.name == original.name);
+ boolean liveGetter =
+ definition.virtualMethods().stream()
+ .anyMatch(method -> method.method.name == original.name);
return liveGetter ? original : null;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index f94954b..a0c9735 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -495,14 +495,16 @@
}
// In compat mode traverse all direct methods in the hierarchy.
if (clazz == startingClass || options.forceProguardCompatibility) {
- Arrays.stream(clazz.directMethods())
+ clazz
+ .directMethods()
.forEach(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
});
}
- Arrays.stream(clazz.virtualMethods())
+ clazz
+ .virtualMethods()
.forEach(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index a51116b..42b7e21 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -30,8 +30,10 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Streams;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
@@ -257,7 +259,7 @@
if (appView.appInfo().neverMerge.contains(clazz.type)) {
return MergeGroup.DONT_MERGE;
}
- if (clazz.staticFields().length + clazz.directMethods().length + clazz.virtualMethods().length
+ if (clazz.staticFields().length + clazz.directMethods().size() + clazz.virtualMethods().size()
== 0) {
return MergeGroup.DONT_MERGE;
}
@@ -268,10 +270,10 @@
.anyMatch(field -> appView.appInfo().isPinned(field.field))) {
return MergeGroup.DONT_MERGE;
}
- if (Arrays.stream(clazz.directMethods()).anyMatch(DexEncodedMethod::isInitializer)) {
+ if (clazz.directMethods().stream().anyMatch(DexEncodedMethod::isInitializer)) {
return MergeGroup.DONT_MERGE;
}
- if (!Arrays.stream(clazz.virtualMethods()).allMatch(DexEncodedMethod::isPrivateMethod)) {
+ if (!clazz.virtualMethods().stream().allMatch(DexEncodedMethod::isPrivateMethod)) {
return MergeGroup.DONT_MERGE;
}
if (Streams.stream(clazz.methods())
@@ -445,7 +447,7 @@
return false;
}
// Check that all of the members are private or public.
- if (!Arrays.stream(clazz.directMethods())
+ if (!clazz.directMethods().stream()
.allMatch(method -> method.accessFlags.isPrivate() || method.accessFlags.isPublic())) {
return false;
}
@@ -458,7 +460,7 @@
// virtual methods are private. Therefore, we don't need to consider check if there are any
// package-private or protected instance fields or virtual methods here.
assert Arrays.stream(clazz.instanceFields()).count() == 0;
- assert Arrays.stream(clazz.virtualMethods()).allMatch(method -> method.accessFlags.isPrivate());
+ assert clazz.virtualMethods().stream().allMatch(method -> method.accessFlags.isPrivate());
// Check that no methods access package-private or protected members.
IllegalAccessDetector registry = new IllegalAccessDetector(appView, clazz);
@@ -489,9 +491,9 @@
numberOfMergedClasses++;
// Move members from source to target.
- targetClass.setDirectMethods(
+ targetClass.appendDirectMethods(
mergeMethods(sourceClass.directMethods(), targetClass.directMethods(), targetClass));
- targetClass.setVirtualMethods(
+ targetClass.appendVirtualMethods(
mergeMethods(sourceClass.virtualMethods(), targetClass.virtualMethods(), targetClass));
targetClass.setStaticFields(
mergeFields(sourceClass.staticFields(), targetClass.staticFields(), targetClass));
@@ -502,30 +504,25 @@
sourceClass.setStaticFields(DexEncodedField.EMPTY_ARRAY);
}
- private DexEncodedMethod[] mergeMethods(
- DexEncodedMethod[] sourceMethods,
- DexEncodedMethod[] targetMethods,
+ private List<DexEncodedMethod> mergeMethods(
+ List<DexEncodedMethod> sourceMethods,
+ List<DexEncodedMethod> targetMethods,
DexProgramClass targetClass) {
- DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.length + targetMethods.length];
-
- // Move all target methods to result.
- System.arraycopy(targetMethods, 0, result, 0, targetMethods.length);
-
// Move source methods to result one by one, renaming them if needed.
MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
Set<Wrapper<DexMethod>> existingMethods =
- Arrays.stream(targetMethods)
+ targetMethods.stream()
.map(targetMethod -> equivalence.wrap(targetMethod.method))
.collect(Collectors.toSet());
Predicate<DexMethod> availableMethodSignatures =
method -> !existingMethods.contains(equivalence.wrap(method));
- int i = targetMethods.length;
+ List<DexEncodedMethod> newMethods = new ArrayList<>(sourceMethods.size());
for (DexEncodedMethod sourceMethod : sourceMethods) {
DexEncodedMethod sourceMethodAfterMove =
renameMethodIfNeeded(sourceMethod, targetClass, availableMethodSignatures);
- result[i++] = sourceMethodAfterMove;
+ newMethods.add(sourceMethodAfterMove);
DexMethod originalMethod =
methodMapping.inverse().getOrDefault(sourceMethod.method, sourceMethod.method);
@@ -533,8 +530,7 @@
existingMethods.add(equivalence.wrap(sourceMethodAfterMove.method));
}
-
- return result;
+ return newMethods;
}
private DexEncodedField[] mergeFields(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 6cf0da0..acc1e78 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -99,8 +100,15 @@
// The class is used and must be kept. Remove the unused fields and methods from
// the class.
usagePrinter.visiting(clazz);
- clazz.setDirectMethods(reachableMethods(clazz.directMethods(), clazz));
- clazz.setVirtualMethods(reachableMethods(clazz.virtualMethods(), clazz));
+ DexEncodedMethod[] reachableDirectMethods = reachableMethods(clazz.directMethods(), clazz);
+ if (reachableDirectMethods != null) {
+ clazz.setDirectMethods(reachableDirectMethods);
+ }
+ DexEncodedMethod[] reachableVirtualMethods =
+ reachableMethods(clazz.virtualMethods(), clazz);
+ if (reachableVirtualMethods != null) {
+ clazz.setVirtualMethods(reachableVirtualMethods);
+ }
clazz.setInstanceFields(reachableFields(clazz.instanceFields()));
clazz.setStaticFields(reachableFields(clazz.staticFields()));
clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
@@ -145,9 +153,9 @@
}
private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
- T[] items, Predicate<S> live) {
- for (int i = 0; i < items.length; i++) {
- if (!live.test(items[i].getKey())) {
+ List<T> items, Predicate<S> live) {
+ for (int i = 0; i < items.size(); i++) {
+ if (!live.test(items.get(i).getKey())) {
return i;
}
}
@@ -159,18 +167,18 @@
&& method.method.proto.parameters.isEmpty();
}
- private DexEncodedMethod[] reachableMethods(DexEncodedMethod[] methods, DexClass clazz) {
+ private DexEncodedMethod[] reachableMethods(List<DexEncodedMethod> methods, DexClass clazz) {
int firstUnreachable = firstUnreachableIndex(methods, appInfo.liveMethods::contains);
// Return the original array if all methods are used.
if (firstUnreachable == -1) {
- return methods;
+ return null;
}
- ArrayList<DexEncodedMethod> reachableMethods = new ArrayList<>(methods.length);
+ ArrayList<DexEncodedMethod> reachableMethods = new ArrayList<>(methods.size());
for (int i = 0; i < firstUnreachable; i++) {
- reachableMethods.add(methods[i]);
+ reachableMethods.add(methods.get(i));
}
- for (int i = firstUnreachable; i < methods.length; i++) {
- DexEncodedMethod method = methods[i];
+ for (int i = firstUnreachable; i < methods.size(); i++) {
+ DexEncodedMethod method = methods.get(i);
if (appInfo.liveMethods.contains(method.getKey())) {
reachableMethods.add(method);
} else if (options.debugKeepRules && isDefaultConstructor(method)) {
@@ -222,7 +230,8 @@
appInfo.liveFields.contains(field)
|| appInfo.fieldsRead.contains(field)
|| appInfo.fieldsWritten.contains(field);
- int firstUnreachable = firstUnreachableIndex(fields, isReachableOrReferencedField);
+ int firstUnreachable =
+ firstUnreachableIndex(Arrays.asList(fields), isReachableOrReferencedField);
// Return the original array if all fields are used.
if (firstUnreachable == -1) {
return fields;
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 145f24b..b68bf80 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClass.MethodSetter;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -993,11 +994,6 @@
return false;
}
- DexEncodedMethod[] mergedDirectMethods =
- mergeMethods(directMethods.values(), target.directMethods());
- DexEncodedMethod[] mergedVirtualMethods =
- mergeMethods(virtualMethods.values(), target.virtualMethods());
-
// Step 2: Merge fields
Set<DexString> existingFieldNames = new HashSet<>();
for (DexEncodedField field : target.fields()) {
@@ -1044,8 +1040,8 @@
? DexTypeList.empty()
: new DexTypeList(interfaces.toArray(new DexType[0]));
// Step 2: replace fields and methods.
- target.setDirectMethods(mergedDirectMethods);
- target.setVirtualMethods(mergedVirtualMethods);
+ target.appendDirectMethods(directMethods.values());
+ target.appendVirtualMethods(virtualMethods.values());
target.setInstanceFields(mergedInstanceFields);
target.setStaticFields(mergedStaticFields);
// Step 3: Unlink old class to ease tree shaking.
@@ -1272,8 +1268,8 @@
}
private DexEncodedMethod[] mergeMethods(
- Collection<DexEncodedMethod> sourceMethods, DexEncodedMethod[] targetMethods) {
- DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.size() + targetMethods.length];
+ Collection<DexEncodedMethod> sourceMethods, List<DexEncodedMethod> targetMethods) {
+ DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.size() + targetMethods.size()];
// Add methods from source.
int i = 0;
for (DexEncodedMethod method : sourceMethods) {
@@ -1281,7 +1277,7 @@
i++;
}
// Add methods from target.
- System.arraycopy(targetMethods, 0, result, i, targetMethods.length);
+ System.arraycopy(targetMethods, 0, result, i, targetMethods.size());
return result;
}
@@ -1425,8 +1421,8 @@
private GraphLense fixupTypeReferences(GraphLense graphLense) {
// Globally substitute merged class types in protos and holders.
for (DexProgramClass clazz : appInfo.classes()) {
- clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
- clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+ fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
+ fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
}
@@ -1440,20 +1436,19 @@
return lense.build(application.dexItemFactory, graphLense);
}
- private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
+ private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
if (methods == null) {
- return null;
+ return;
}
- for (int i = 0; i < methods.length; i++) {
- DexEncodedMethod encodedMethod = methods[i];
+ for (int i = 0; i < methods.size(); i++) {
+ DexEncodedMethod encodedMethod = methods.get(i);
DexMethod method = encodedMethod.method;
DexMethod newMethod = fixupMethod(method);
if (newMethod != method) {
lense.move(method, newMethod);
- methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
}
}
- return methods;
}
private DexEncodedField[] substituteTypesIn(DexEncodedField[] fields) {
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 cad6397..86cb9d0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -20,15 +20,18 @@
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.naming.InterfaceMethodNameMinifier;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering;
+import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -218,6 +221,9 @@
}
public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter();
+ public Set<String> extensiveInterfaceMethodMinifierLoggingFilter =
+ getExtensiveInterfaceMethodMinifierLoggingFilter();
+
public List<String> methodsFilter = ImmutableList.of();
public int minApiLevel = AndroidApiLevel.getDefault().getLevel();
// Skipping min_api check and compiling an intermediate result intended for later merging.
@@ -288,6 +294,19 @@
return ImmutableSet.of();
}
+ private static Set<String> getExtensiveInterfaceMethodMinifierLoggingFilter() {
+ String property =
+ System.getProperty("com.android.tools.r8.extensiveInterfaceMethodMinifierLoggingFilter");
+ if (property != null) {
+ ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+ for (String method : property.split(";")) {
+ builder.add(method);
+ }
+ return builder.build();
+ }
+ return ImmutableSet.of();
+ }
+
public static class InvalidParameterAnnotationInfo {
final DexMethod method;
@@ -532,6 +551,21 @@
public boolean noLocalsTableOnInput = false;
public boolean forceNameReflectionOptimization = false;
public boolean disallowLoadStoreOptimization = false;
+
+ public MinifierTestingOptions minifier = new MinifierTestingOptions();
+
+ public static class MinifierTestingOptions {
+
+ public Comparator<DexMethod> interfaceMethodOrdering = null;
+
+ public Comparator<Wrapper<DexMethod>> createInterfaceMethodOrdering(
+ InterfaceMethodNameMinifier minifier) {
+ if (interfaceMethodOrdering != null) {
+ return (a, b) -> interfaceMethodOrdering.compare(a.get(), b.get());
+ }
+ return minifier.createDefaultInterfaceMethodOrdering();
+ }
+ }
}
private boolean hasMinApi(AndroidApiLevel level) {
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index c9be1b8..f5a4b81 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -341,7 +341,7 @@
private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByRenamedName(
NamingLens namingLens, DexProgramClass clazz) {
IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
- new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
+ new IdentityHashMap<>(clazz.directMethods().size() + clazz.virtualMethods().size());
for (DexEncodedMethod method : clazz.methods()) {
// Add method only if renamed or contains positions.
DexString renamedName = namingLens.lookupName(method.method);
diff --git a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
index dd4c73a..33f9d7c 100644
--- a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
@@ -7,48 +7,49 @@
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
import java.util.Iterator;
+import java.util.List;
import java.util.NoSuchElementException;
public class OrderedMergingIterator<T extends KeyedDexItem<S>, S extends PresortedComparable<S>>
implements Iterator<T> {
- private final T[] one;
- private final T[] other;
+ private final List<T> one;
+ private final List<T> other;
private int oneIndex = 0;
private int otherIndex = 0;
- public OrderedMergingIterator(T[] one, T[] other) {
+ public OrderedMergingIterator(List<T> one, List<T> other) {
this.one = one;
this.other = other;
}
- private static <T> T getNextChecked(T[] array, int position) {
- if (position >= array.length) {
+ private static <T> T getNextChecked(List<T> list, int position) {
+ if (position >= list.size()) {
throw new NoSuchElementException();
}
- return array[position];
+ return list.get(position);
}
@Override
public boolean hasNext() {
- return oneIndex < one.length || otherIndex < other.length;
+ return oneIndex < one.size() || otherIndex < other.size();
}
@Override
public T next() {
- if (oneIndex >= one.length) {
+ if (oneIndex >= one.size()) {
return getNextChecked(other, otherIndex++);
}
- if (otherIndex >= other.length) {
+ if (otherIndex >= other.size()) {
return getNextChecked(one, oneIndex++);
}
- int comparison = one[oneIndex].getKey().compareTo(other[otherIndex].getKey());
+ int comparison = one.get(oneIndex).getKey().compareTo(other.get(otherIndex).getKey());
if (comparison < 0) {
- return one[oneIndex++];
+ return one.get(oneIndex++);
}
if (comparison == 0) {
throw new InternalCompilerError("Source arrays are not disjoint.");
}
- return other[otherIndex++];
+ return other.get(otherIndex++);
}
}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 55730eb..ecf247c 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -57,7 +57,6 @@
return self();
}
-
public String proguardMap() {
return proguardMap;
}
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 36a4f00..bfd929e 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -45,7 +45,7 @@
IRConverter converter = new IRConverter(new AppInfoWithSubtyping(application), options);
converter.optimize(application);
DexProgramClass clazz = application.classes().iterator().next();
- assertEquals(4, clazz.directMethods().length);
+ assertEquals(4, clazz.directMethods().size());
for (DexEncodedMethod method : clazz.directMethods()) {
if (!method.method.name.toString().equals("main")) {
assertEquals(2, method.getCode().asDexCode().instructions.length);
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
index 3d75846..a958c3a 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -72,7 +72,7 @@
private int countCatchHandlers(AndroidApp inputApp) throws Exception {
CodeInspector inspector = new CodeInspector(inputApp);
DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
- Code code = dexClass.virtualMethods()[0].getCode();
+ Code code = dexClass.virtualMethods().get(0).getCode();
if (code.isCfCode()) {
CfCode cfCode = code.asCfCode();
Set<CfLabel> targets = Sets.newIdentityHashSet();
diff --git a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
index 2664ef4..5d536cf 100644
--- a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
@@ -71,7 +71,7 @@
private int countCatchHandlers(AndroidApp inputApp) throws Exception {
CodeInspector inspector = new CodeInspector(inputApp);
DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
- Code code = dexClass.virtualMethods()[0].getCode();
+ Code code = dexClass.virtualMethods().get(0).getCode();
if (code.isCfCode()) {
CfCode cfCode = code.asCfCode();
Set<CfLabel> targets = Sets.newIdentityHashSet();
diff --git a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
index 68daeb8..9c9e785 100644
--- a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
+++ b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
@@ -59,11 +59,11 @@
assertThat(clazz, isPresent());
// There are two direct methods, but only because one is <init>.
- assertEquals(2, clazz.getDexClass().directMethods().length);
+ assertEquals(2, clazz.getDexClass().directMethods().size());
assertThat(clazz.method("void", "<init>", ImmutableList.of()), isPresent());
// There is only one virtual method.
- assertEquals(1, clazz.getDexClass().virtualMethods().length);
+ assertEquals(1, clazz.getDexClass().virtualMethods().size());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index b8c4b47..7e9269d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -183,7 +183,7 @@
if (check.match(clazz)) {
// Validate static initializer.
if (check instanceof Group) {
- assertEquals(clazz.directMethods().length, ((Group) check).singletons == 0 ? 1 : 2);
+ assertEquals(clazz.directMethods().size(), ((Group) check).singletons == 0 ? 1 : 2);
}
list.remove(clazz);
@@ -228,8 +228,8 @@
} else {
assertTrue(isJStyleLambdaOrGroup(clazz));
// Taking the number of any virtual method parameters seems to be good enough.
- assertTrue(clazz.virtualMethods().length > 0);
- return clazz.virtualMethods()[0].method.proto.parameters.size();
+ assertTrue(clazz.virtualMethods().size() > 0);
+ return clazz.virtualMethods().get(0).method.proto.parameters.size();
}
fail("Failed to get arity for " + clazz.type.descriptor.toString());
throw new AssertionError();
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java
new file mode 100644
index 0000000..f2e1a60
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java
@@ -0,0 +1,69 @@
+// 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;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.FileUtils;
+import java.nio.file.Path;
+import org.junit.Test;
+
+/** Regression test for b/123730537. */
+public class InterfaceMethodNameMinifierTest extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+ FileUtils.writeTextFile(dictionary, "a", "b", "c");
+
+ testForR8(Backend.DEX)
+ .addInnerClasses(InterfaceMethodNameMinifierTest.class)
+ .addKeepRules(
+ "-keep,allowobfuscation interface * { <methods>; }",
+ "-keep,allowobfuscation class * { <methods>; }",
+ "-keep interface " + L.class.getTypeName() + " { <methods>; }",
+ "-obfuscationdictionary " + dictionary.toString())
+ // Minify the interface methods in alphabetic order.
+ .addOptionsModification(
+ options -> options.testing.minifier.interfaceMethodOrdering = DexMethod::slowCompareTo)
+ .compile();
+ }
+
+ interface I {}
+
+ interface J extends I {
+
+ // Will be renamed first, to a().
+ void a();
+
+ // Will be renamed secondly. Should be renamed to c(), and not b(), because `void K.b()` will
+ // be renamed to b() because `void L.b()` is reserved.
+ void c();
+ }
+
+ interface K extends I {
+
+ // Will be renamed thirdly, to b(), because `void L.b()` is reserved.
+ void b();
+ }
+
+ interface L {
+
+ // Reserved. Will be renamed together with `void K.b()`.
+ void b();
+ }
+
+ static class Implementation implements J, K {
+
+ @Override
+ public void a() {}
+
+ @Override
+ public void b() {}
+
+ @Override
+ public void c() {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
index d28770a..5b02222 100644
--- a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -100,7 +100,7 @@
assertThat(classSubject, isPresent());
assertThat(classSubject, not(isRenamed()));
DexClass clazz = classSubject.getDexClass();
- assertEquals(3, clazz.virtualMethods().length);
+ assertEquals(3, clazz.virtualMethods().size());
for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
MethodSubject methodSubject =
@@ -141,7 +141,7 @@
assertThat(classSubject, isPresent());
assertThat(classSubject, not(isRenamed()));
DexClass clazz = classSubject.getDexClass();
- assertEquals(3, clazz.virtualMethods().length);
+ assertEquals(3, clazz.virtualMethods().size());
for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
MethodSubject methodSubject =
@@ -163,7 +163,7 @@
assertThat(classSubject, isPresent());
assertThat(classSubject, not(isRenamed()));
DexClass clazz = classSubject.getDexClass();
- assertEquals(3, clazz.virtualMethods().length);
+ assertEquals(3, clazz.virtualMethods().size());
for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
MethodSubject methodSubject =
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index eacc338..bfeac7a 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -848,13 +848,13 @@
CodeInspector inspector = new CodeInspector(processedApplication);
ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
assertTrue(clazz.isPresent());
- assertEquals(3, clazz.getDexClass().directMethods().length);
+ assertEquals(3, clazz.getDexClass().directMethods().size());
// Collect the return types of the putlines for the body of method1 and method2.
List<DexType> r = new ArrayList<>();
- for (int i = 0; i < clazz.getDexClass().directMethods().length; i++) {
- if (clazz.getDexClass().directMethods()[i].getCode().asDexCode().instructions[0]
+ for (int i = 0; i < clazz.getDexClass().directMethods().size(); i++) {
+ if (clazz.getDexClass().directMethods().get(i).getCode().asDexCode().instructions[0]
instanceof InvokeVirtual) {
- r.add(clazz.getDexClass().directMethods()[i].method.proto.returnType);
+ r.add(clazz.getDexClass().directMethods().get(i).method.proto.returnType);
}
}
assert r.size() == 2;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index d870316..fe98075 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -185,7 +185,7 @@
}
static <S, T extends Subject> void forAll(
- S[] items,
+ List<? extends S> items,
BiFunction<S, FoundClassSubject, ? extends T> constructor,
FoundClassSubject clazz,
Consumer<T> consumer) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index ed4a67f..ebaf5af 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -84,7 +85,7 @@
: new FoundMethodSubject(codeInspector, encoded, this);
}
- private DexEncodedMethod findMethod(DexEncodedMethod[] methods, DexMethod dexMethod) {
+ private DexEncodedMethod findMethod(List<DexEncodedMethod> methods, DexMethod dexMethod) {
for (DexEncodedMethod method : methods) {
if (method.method.equals(dexMethod)) {
return method;
@@ -108,12 +109,12 @@
@Override
public void forAllFields(Consumer<FoundFieldSubject> inspection) {
CodeInspector.forAll(
- dexClass.staticFields(),
+ Arrays.asList(dexClass.staticFields()),
(dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
this,
inspection);
CodeInspector.forAll(
- dexClass.instanceFields(),
+ Arrays.asList(dexClass.instanceFields()),
(dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
this,
inspection);