Use original type/signature when checking noObfuscation.
Otherwise, after certain optimizations that may change signatures,
items that are expected not to be renamed could be renamed.
Bug: 130791310
Change-Id: Ia23b0769d6fae8e159edb6a24b0d69adea95cf4d
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 725f500..f37e60f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
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;
import com.android.tools.r8.graph.InnerClassAttribute;
@@ -105,7 +104,7 @@
// Collect names we have to keep.
timing.begin("reserve");
for (DexClass clazz : classes) {
- if (classNamingStrategy.noObfuscation().contains(clazz.type)) {
+ if (classNamingStrategy.noObfuscation(clazz.type)) {
assert !renaming.containsKey(clazz.type);
registerClassAsUsed(clazz.type);
}
@@ -183,7 +182,7 @@
private void renameDanglingType(DexType type) {
if (appView.appInfo().wasPruned(type)
&& !renaming.containsKey(type)
- && !classNamingStrategy.noObfuscation().contains(type)) {
+ && !classNamingStrategy.noObfuscation(type)) {
// We have a type that is defined in the program source but is only used in a proto or
// return type. As we don't need the class, we can rename it to anything as long as it is
// unique.
@@ -201,7 +200,7 @@
DexType outerClass = getOutClassForType(type);
if (outerClass != null) {
if (!renaming.containsKey(outerClass)
- && !classNamingStrategy.noObfuscation().contains(outerClass)) {
+ && !classNamingStrategy.noObfuscation(outerClass)) {
// The outer class was not previously kept and will not be kept.
// We have to force keep the outer class now.
registerClassAsUsed(outerClass);
@@ -451,7 +450,7 @@
boolean bypassDictionary();
- Set<DexReference> noObfuscation();
+ boolean noObfuscation(DexType type);
}
protected interface PackageNamingStrategy {
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 06c2909..991805a 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -114,7 +114,8 @@
return true;
}
if (!appView.options().getProguardConfiguration().hasApplyMappingFile()
- && appView.rootSet().noObfuscation.contains(field.field)) {
+ && appView.rootSet().noObfuscation.contains(
+ appView.graphLense().getOriginalFieldSignature(field.field))) {
return true;
}
return false;
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index 5c37c46..8a58da2 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
-import java.util.Set;
public interface MemberNamingStrategy {
@@ -20,5 +19,5 @@
boolean breakOnNotAvailable(DexReference source, DexString name);
- Set<DexReference> noObfuscation();
+ boolean noObfuscation(DexReference reference);
}
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 a8892d8..ee1b64d 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -293,7 +293,7 @@
// methods?
if (keepAll
|| method.accessFlags.isConstructor()
- || strategy.noObfuscation().contains(method.method)) {
+ || strategy.noObfuscation(method.method)) {
reserveNamesForMethod(method.method, state);
}
}
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 e9dc45b..264167e 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
@@ -46,8 +45,7 @@
ClassNameMinifier classNameMinifier =
new ClassNameMinifier(
appView,
- new MinificationClassNamingStrategy(
- appView.dexItemFactory(), appView.rootSet().noObfuscation),
+ new MinificationClassNamingStrategy(appView),
new MinificationPackageNamingStrategy(),
// Use deterministic class order to make sure renaming is deterministic.
appView.appInfo().classesWithDeterministicOrder());
@@ -58,8 +56,7 @@
appView, classRenaming, MethodRenaming.empty(), FieldRenaming.empty())
.verifyNoCollisions(appView.appInfo().classes(), appView.dexItemFactory());
- MemberNamingStrategy minifyMembers =
- new MinifierMemberNamingStrategy(appView.dexItemFactory(), appView.rootSet().noObfuscation);
+ MemberNamingStrategy minifyMembers = new MinifierMemberNamingStrategy(appView);
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
new MethodNameMinifier(appView, minifyMembers)
@@ -85,13 +82,11 @@
static class MinificationClassNamingStrategy implements ClassNamingStrategy {
- private final DexItemFactory factory;
+ private final AppView<?> appView;
private final Object2IntMap<Namespace> namespaceCounters = new Object2IntLinkedOpenHashMap<>();
- private final Set<DexReference> noObfuscation;
- MinificationClassNamingStrategy(DexItemFactory factory, Set<DexReference> noObfuscation) {
- this.factory = factory;
- this.noObfuscation = noObfuscation;
+ MinificationClassNamingStrategy(AppView<?> appView) {
+ this.appView = appView;
namespaceCounters.defaultReturnValue(1);
}
@@ -99,7 +94,8 @@
public DexString next(Namespace namespace, DexType type, char[] packagePrefix) {
int counter = namespaceCounters.put(namespace, namespaceCounters.getInt(namespace) + 1);
DexString string =
- factory.createString(StringUtils.numberToIdentifier(packagePrefix, counter, true));
+ appView.dexItemFactory()
+ .createString(StringUtils.numberToIdentifier(packagePrefix, counter, true));
return string;
}
@@ -109,8 +105,8 @@
}
@Override
- public Set<DexReference> noObfuscation() {
- return noObfuscation;
+ public boolean noObfuscation(DexType type) {
+ return appView.rootSet().noObfuscation.contains(appView.graphLense().getOriginalType(type));
}
}
@@ -142,18 +138,17 @@
public static char[] EMPTY_CHAR_ARRAY = new char[0];
- private final DexItemFactory factory;
- private final Set<DexReference> noObfuscation;
+ private final AppView<?> appView;
- public MinifierMemberNamingStrategy(DexItemFactory factory, Set<DexReference> noObfuscation) {
- this.factory = factory;
- this.noObfuscation = noObfuscation;
+ public MinifierMemberNamingStrategy(AppView<?> appView) {
+ this.appView = appView;
}
@Override
public DexString next(DexMethod method, MethodNamingState.InternalState internalState) {
int counter = internalState.incrementAndGet();
- return factory.createString(StringUtils.numberToIdentifier(EMPTY_CHAR_ARRAY, counter, false));
+ return appView.dexItemFactory()
+ .createString(StringUtils.numberToIdentifier(EMPTY_CHAR_ARRAY, counter, false));
}
@Override
@@ -172,8 +167,15 @@
}
@Override
- public Set<DexReference> noObfuscation() {
- return noObfuscation;
+ public boolean noObfuscation(DexReference reference) {
+ if (reference.isDexField()) {
+ return appView.rootSet().noObfuscation.contains(
+ appView.graphLense().getOriginalFieldSignature(reference.asDexField()));
+ } else {
+ assert reference.isDexMethod();
+ return appView.rootSet().noObfuscation.contains(
+ appView.graphLense().getOriginalMethodSignature(reference.asDexMethod()));
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index afdc1e5..a3f5ca5 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -28,7 +28,6 @@
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.Timing;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -200,8 +199,6 @@
static class ApplyMappingClassNamingStrategy implements ClassNamingStrategy {
private final Map<DexType, DexString> mappings;
- // We have an explicit mapping from the proguard map thus everything might have to be renamed.
- private final Set<DexReference> noObfuscation = new HashSet<>();
ApplyMappingClassNamingStrategy(Map<DexType, DexString> mappings) {
this.mappings = mappings;
@@ -218,8 +215,9 @@
}
@Override
- public Set<DexReference> noObfuscation() {
- return noObfuscation;
+ public boolean noObfuscation(DexType type) {
+ // We have an explicit mapping from the proguard map thus everything might have to be renamed.
+ return false;
}
}
@@ -228,8 +226,6 @@
private final Map<DexReference, MemberNaming> mappedNames;
private final DexItemFactory factory;
private final Reporter reporter;
- // We have an explicit mapping from the proguard map thus everything might have to be renamed.
- private final Set<DexReference> noObfuscation = new HashSet<>();
public ApplyMappingMemberNamingStrategy(
Map<DexReference, MemberNaming> mappedNames, DexItemFactory factory, Reporter reporter) {
@@ -284,8 +280,9 @@
}
@Override
- public Set<DexReference> noObfuscation() {
- return noObfuscation;
+ public boolean noObfuscation(DexReference reference) {
+ // We have an explicit mapping from the proguard map thus everything might have to be renamed.
+ return false;
}
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
new file mode 100644
index 0000000..40482d3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
@@ -0,0 +1,145 @@
+// 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.b130791310;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.ProguardTestBuilder;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class TestClass {
+ SomeLogic instance;
+
+ public void create() {
+ instance = new SomeLogic(new Object());
+ }
+
+ void caller() {
+ if (instance.someMethod(new SomeClass()) == null) {
+ System.out.println("TestMain#caller");
+ }
+ }
+
+ public static void main(String[] args) {
+ TestClass m = new TestClass();
+ m.create();
+ m.caller();
+ }
+}
+
+class SomeLogic {
+ Object otherField;
+
+ public SomeLogic(Object o) {
+ otherField = o;
+ }
+
+ public SomeInterface someMethod(SomeClass list) {
+ System.out.println("list is" + ((list == null) ? "" : " not") + " null");
+ return null;
+ }
+}
+
+interface SomeInterface {
+ byte foo();
+}
+
+class SomeClass implements SomeInterface {
+ @Override
+ public byte foo() {
+ return 0x8;
+ }
+}
+
+// SomeInterface has a single implementer SomeClass: vertical merging candidate.
+// If -keepnames specifies a member whose signature can be changed due to vertical merging, their
+// names are not preserved because that rule allows shrinking (and optimization) implicitly.
+// According to b/130791310, Proguard (via AGP) still keeps that name, but this reproduction shows
+// that both R8 and Proguard ignores -keepnames. It turns out that, as per
+// https://android.googlesource.com/platform/sdk/+/master/files/proguard-android-optimize.txt#13
+// class merging is disabled for Proguard.
+@RunWith(Parameterized.class)
+public class B130791310 extends TestBase {
+ private static final Class<?> MAIN = TestClass.class;
+ private static final List<Class<?>> CLASSES =
+ ImmutableList.of(MAIN, SomeLogic.class, SomeInterface.class, SomeClass.class);
+ private static final String RULES = StringUtils.lines(
+ "-keepnames class **.SomeLogic {",
+ " **.SomeInterface someMethod(**.SomeClass);",
+ "}"
+ );
+
+ private final boolean enableClassMerging;
+
+ @Parameterized.Parameters(name = "enable class merging: {0}")
+ public static Boolean[] data() {
+ return BooleanUtils.values();
+ }
+
+ public B130791310(boolean enableClassMerging) {
+ this.enableClassMerging = enableClassMerging;
+ }
+
+ private void inspect(CodeInspector inspector, boolean isR8) {
+ ClassSubject holder = inspector.clazz(SomeLogic.class);
+ assertThat(holder, isPresent());
+ assertThat(holder, not(isRenamed()));
+ MethodSubject someMethod = holder.uniqueMethodWithName("someMethod");
+ if (enableClassMerging && !isR8) {
+ // Note that the method is not entirely gone, but merged to the implementer, along with some
+ // method signature modification.
+ assertThat(someMethod, not(isPresent()));
+ } else {
+ assertThat(someMethod, isPresent());
+ assertThat(someMethod, not(isRenamed()));
+ }
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ ProguardTestBuilder builder =
+ testForProguard()
+ .addProgramClasses(CLASSES)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addKeepClassAndMembersRules(MAIN)
+ .addKeepRules(RULES);
+ if (!enableClassMerging) {
+ builder.addKeepRules("-optimizations !class/merging/*");
+ }
+ builder
+ .compile()
+ .inspect(inspector -> inspect(inspector, false));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(Backend.DEX)
+ .addProgramClasses(CLASSES)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addKeepClassAndMembersRules(MAIN)
+ .addKeepRules(RULES);
+ if (!enableClassMerging) {
+ builder.addOptionsModification(o -> o.enableVerticalClassMerging = false);
+ }
+ builder
+ .compile()
+ .inspect(inspector -> inspect(inspector, true));
+ }
+}