Fix interface method minification bug
Bug: 123730537
Change-Id: I0596457e48ff1f6bd03acea76442f9ffd12f615a
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index a511b0a..b643be3 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -36,21 +36,6 @@
public class InterfaceMethodNameMinifier {
- public interface InterfaceMethodOrdering {
-
- Comparator<Wrapper<DexMethod>> getComparator(InterfaceMethodNameMinifier minifier);
- }
-
- public static class DefaultInterfaceMethodOrdering implements InterfaceMethodOrdering {
-
- @Override
- public Comparator<Wrapper<DexMethod>> getComparator(InterfaceMethodNameMinifier minifier) {
- Map<Wrapper<DexMethod>, Set<NamingState<DexProto, ?>>> globalStateMap =
- minifier.globalStateMap;
- return (a, b) -> globalStateMap.get(b).size() - globalStateMap.get(a).size();
- }
- }
-
private final AppInfoWithLiveness appInfo;
private final Set<DexCallSite> desugaredCallSites;
private final Equivalence<DexMethod> equivalence;
@@ -87,6 +72,10 @@
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;
}
@@ -180,10 +169,27 @@
List<Wrapper<DexMethod>> interfaceMethods =
globalStateMap.keySet().stream()
.filter(wrapper -> unificationParent.getOrDefault(wrapper, wrapper).equals(wrapper))
- .sorted(options.testing.minifier.interfaceMethodOrdering.getComparator(this))
+ .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) {
- assignNameToInterfaceMethod(key, unification);
+ if (!reservedInterfaceMethods.contains(key)) {
+ assignNameToInterfaceMethod(key, unification);
+ }
}
for (Entry<DexCallSite, DexMethod> entry : callSites.entrySet()) {
@@ -200,6 +206,24 @@
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<>();
@@ -233,13 +257,8 @@
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;
- }
+ 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.
@@ -282,6 +301,23 @@
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<>();
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 a28c107..85c4876 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -21,17 +21,18 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.optimize.Inliner;
-import com.android.tools.r8.naming.InterfaceMethodNameMinifier.DefaultInterfaceMethodOrdering;
-import com.android.tools.r8.naming.InterfaceMethodNameMinifier.InterfaceMethodOrdering;
+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;
@@ -566,7 +567,15 @@
public static class MinifierTestingOptions {
- public InterfaceMethodOrdering interfaceMethodOrdering = new DefaultInterfaceMethodOrdering();
+ 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();
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java
index 4315486..f2e1a60 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java
@@ -4,10 +4,8 @@
package com.android.tools.r8.naming;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.CompilationFailedException;
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;
@@ -17,28 +15,20 @@
@Test
public void test() throws Exception {
- try {
- Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
- FileUtils.writeTextFile(dictionary, "a", "b", "c");
+ 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 =
- minifier -> (a, b) -> a.get().slowCompareTo(b.get()))
- .compile();
- } catch (CompilationFailedException e) {
- // TODO(b/123730537): Fails with duplicate methods.
- return;
- }
- fail();
+ 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 {}