Use keepclassmembers when applicable.
Bug: b/248408342
Change-Id: I4df4b973d3614e36a575f17f17271b0d631b1c25
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
index c6acc4f..10db385 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
@@ -78,6 +78,16 @@
public boolean isExact() {
return false;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == this;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
}
private static final class KeepPackageTopPattern extends KeepPackageExactPattern {
@@ -111,6 +121,7 @@
private final String fullPackage;
private KeepPackageExactPattern(String fullPackage) {
+ assert fullPackage != null;
this.fullPackage = fullPackage;
// TODO: Verify valid package identifiers.
}
@@ -138,6 +149,23 @@
public String getExactPackageAsString() {
return fullPackage;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepPackageExactPattern that = (KeepPackageExactPattern) o;
+ return fullPackage.equals(that.fullPackage);
+ }
+
+ @Override
+ public int hashCode() {
+ return fullPackage.hashCode();
+ }
}
public abstract boolean isAny();
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
index 3f2433d..9485580 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
@@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.experimental.keepanno.ast;
-public class KeepQualifiedClassNamePattern {
+import java.util.Objects;
+
+public final class KeepQualifiedClassNamePattern {
public static Builder builder() {
return new Builder();
@@ -62,6 +64,8 @@
public KeepQualifiedClassNamePattern(
KeepPackagePattern packagePattern, KeepUnqualfiedClassNamePattern namePattern) {
+ assert packagePattern != null;
+ assert namePattern != null;
this.packagePattern = packagePattern;
this.namePattern = namePattern;
}
@@ -77,4 +81,21 @@
public KeepUnqualfiedClassNamePattern getNamePattern() {
return namePattern;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepQualifiedClassNamePattern that = (KeepQualifiedClassNamePattern) o;
+ return packagePattern.equals(that.packagePattern) && namePattern.equals(that.namePattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(packagePattern.hashCode(), namePattern.hashCode());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
index 7c6b6ba..ab38ed2 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -61,6 +61,16 @@
public boolean isExact() {
return false;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
}
public static class KeepClassNameExactPattern extends KeepUnqualfiedClassNamePattern {
@@ -68,6 +78,7 @@
private final String className;
private KeepClassNameExactPattern(String className) {
+ assert className != null;
this.className = className;
}
@@ -89,6 +100,23 @@
public String getExactNameAsString() {
return className;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepClassNameExactPattern that = (KeepClassNameExactPattern) o;
+ return className.equals(that.className);
+ }
+
+ @Override
+ public int hashCode() {
+ return className.hashCode();
+ }
}
public abstract boolean isAny();
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java b/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
index 4bda1fa..85c9f00 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.experimental.keepanno.ast.KeepPackagePattern;
import com.android.tools.r8.experimental.keepanno.ast.KeepPreconditions;
import com.android.tools.r8.experimental.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepTarget;
import com.android.tools.r8.experimental.keepanno.ast.KeepTypePattern;
import com.android.tools.r8.experimental.keepanno.ast.KeepUnqualfiedClassNamePattern;
import com.android.tools.r8.utils.StringUtils;
@@ -42,18 +43,7 @@
private List<ItemRule> getConsequentRules(KeepConsequences consequences) {
List<ItemRule> consequentItems = new ArrayList<>();
- consequences.forEachTarget(
- target ->
- target
- .getItem()
- .match(
- () -> consequentItems.add(new ItemRule("class * { *; }", target.getOptions())),
- clazzPattern -> {
- StringBuilder builder = new StringBuilder();
- printClassItem(builder, clazzPattern);
- consequentItems.add(new ItemRule(builder.toString(), target.getOptions()));
- return null;
- }));
+ consequences.forEachTarget(target -> consequentItems.add(new ItemRule(target)));
return consequentItems;
}
@@ -78,11 +68,16 @@
consequentItem -> {
// Since conjunctions are not supported in keep rules, we expand them into
// disjunctions so conservatively we keep the consequences if any one of
- // the
- // preconditions hold.
- StringBuilder builder = new StringBuilder("-if ");
- printClassItem(builder, conditionItem);
- builder.append(' ');
+ // the preconditions hold.
+ StringBuilder builder = new StringBuilder();
+ if (!consequentItem.isMemberConsequent()
+ || !conditionItem
+ .getClassNamePattern()
+ .equals(consequentItem.getHolderPattern())) {
+ builder.append("-if ");
+ printClassItem(builder, conditionItem);
+ builder.append(' ');
+ }
printConsequentRule(builder, consequentItem);
ruleConsumer.accept(builder.toString());
});
@@ -98,13 +93,17 @@
}
private static StringBuilder printConsequentRule(StringBuilder builder, ItemRule rule) {
- builder.append("-keep");
+ if (rule.isMemberConsequent()) {
+ builder.append("-keepclassmembers");
+ } else {
+ builder.append("-keep");
+ }
for (KeepOption option : KeepOption.values()) {
if (rule.options.allow(option)) {
builder.append(",allow").append(getOptionString(option));
}
}
- return builder.append(" ").append(rule.ruleLine);
+ return builder.append(" ").append(rule.getKeepRuleForItem());
}
private static StringBuilder printClassItem(
@@ -229,12 +228,35 @@
}
private static class ItemRule {
- private final String ruleLine;
+ private final KeepTarget target;
private final KeepOptions options;
+ private String ruleLine = null;
- public ItemRule(String ruleLine, KeepOptions options) {
- this.ruleLine = ruleLine;
- this.options = options;
+ public ItemRule(KeepTarget target) {
+ this.target = target;
+ this.options = target.getOptions();
+ }
+
+ public boolean isMemberConsequent() {
+ return target.getItem().match(() -> false, clazz -> !clazz.getMembersPattern().isNone());
+ }
+
+ public KeepQualifiedClassNamePattern getHolderPattern() {
+ return target
+ .getItem()
+ .match(KeepQualifiedClassNamePattern::any, KeepClassPattern::getClassNamePattern);
+ }
+
+ public String getKeepRuleForItem() {
+ if (ruleLine == null) {
+ ruleLine =
+ target
+ .getItem()
+ .match(
+ () -> "class * { *; }",
+ clazz -> printClassItem(new StringBuilder(), clazz).toString());
+ }
+ return ruleLine;
}
}
}
diff --git a/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
index 2a5f4d6..3319a99 100644
--- a/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
+++ b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
@@ -17,6 +17,8 @@
@RunWith(Parameterized.class)
public class KeepEdgeApiTest extends TestBase {
+ private static String CLASS = "com.example.Foo";
+
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
@@ -47,19 +49,14 @@
@Test
public void testKeepClass() throws Exception {
- KeepQualifiedClassNamePattern clazz = KeepQualifiedClassNamePattern.exact("com.example.Foo");
- KeepItemPattern item = KeepItemPattern.builder().setClassPattern(clazz).build();
- KeepTarget target = KeepTarget.builder().setItem(item).build();
+ KeepTarget target = target(classItem(CLASS));
KeepConsequences consequences = KeepConsequences.builder().addTarget(target).build();
KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
- assertEquals(StringUtils.unixLines("-keep class com.example.Foo"), extract(edge));
+ assertEquals(StringUtils.unixLines("-keep class " + CLASS), extract(edge));
}
@Test
public void testKeepInitIfReferenced() throws Exception {
- // Equivalent of: -if class com.example.Foo -keep class com.example.Foo { void <init>(); }
- KeepQualifiedClassNamePattern classPattern =
- KeepQualifiedClassNamePattern.exact("com.example.Foo");
KeepEdge edge =
KeepEdge.builder()
.setPreconditions(
@@ -67,35 +64,90 @@
.addCondition(
KeepCondition.builder()
.setUsageKind(KeepUsageKind.symbolicReference())
- .setItem(
- KeepItemPattern.builder().setClassPattern(classPattern).build())
+ .setItem(classItem(CLASS))
.build())
.build())
.setConsequences(
KeepConsequences.builder()
.addTarget(
- KeepTarget.builder()
- .setItem(
- KeepItemPattern.builder()
- .setClassPattern(classPattern)
- .setMembersPattern(
- KeepMembersPattern.builder()
- .addMethodPattern(
- KeepMethodPattern.builder()
- .setNamePattern(
- KeepMethodNamePattern.initializer())
- .setParametersPattern(
- KeepMethodParametersPattern.none())
- .setReturnTypeVoid()
- .build())
- .build())
- .build())
+ target(
+ buildClassItem(CLASS)
+ .setMembersPattern(defaultInitializerPattern())
+ .build()))
+ .build())
+ .build();
+ assertEquals(
+ StringUtils.unixLines("-keepclassmembers class " + CLASS + " { void <init>(); }"),
+ extract(edge));
+ }
+
+ @Test
+ public void testKeepInstanceIfReferenced() throws Exception {
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setPreconditions(
+ KeepPreconditions.builder()
+ .addCondition(
+ KeepCondition.builder()
+ .setUsageKind(KeepUsageKind.symbolicReference())
+ .setItem(classItem(CLASS))
.build())
.build())
+ .setConsequences(KeepConsequences.builder().addTarget(target(classItem(CLASS))).build())
+ .build();
+ assertEquals(
+ StringUtils.unixLines("-if class " + CLASS + " -keep class " + CLASS), extract(edge));
+ }
+
+ @Test
+ public void testKeepInstanceAndInitIfReferenced() throws Exception {
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setPreconditions(
+ KeepPreconditions.builder()
+ .addCondition(
+ KeepCondition.builder()
+ .setUsageKind(KeepUsageKind.symbolicReference())
+ .setItem(classItem(CLASS))
+ .build())
+ .build())
+ .setConsequences(
+ KeepConsequences.builder()
+ .addTarget(target(classItem(CLASS)))
+ .addTarget(
+ target(
+ buildClassItem(CLASS)
+ .setMembersPattern(defaultInitializerPattern())
+ .build()))
+ .build())
.build();
assertEquals(
StringUtils.unixLines(
- "-if class com.example.Foo -keep class com.example.Foo { void <init>(); }"),
+ "-if class " + CLASS + " -keep class " + CLASS,
+ "-keepclassmembers class " + CLASS + " { void <init>(); }"),
extract(edge));
}
+
+ private KeepTarget target(KeepItemPattern item) {
+ return KeepTarget.builder().setItem(item).build();
+ }
+
+ private KeepItemPattern classItem(String typeName) {
+ return buildClassItem(typeName).build();
+ }
+
+ private KeepItemPattern.Builder buildClassItem(String typeName) {
+ return KeepItemPattern.builder().setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
+ }
+
+ private KeepMembersPattern defaultInitializerPattern() {
+ return KeepMembersPattern.builder()
+ .addMethodPattern(
+ KeepMethodPattern.builder()
+ .setNamePattern(KeepMethodNamePattern.initializer())
+ .setParametersPattern(KeepMethodParametersPattern.none())
+ .setReturnTypeVoid()
+ .build())
+ .build();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java
index bdb4da5..2e726b0 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor;
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.ProguardVersion;
@@ -43,20 +44,21 @@
@Test
public void testCompatR8() throws Exception {
- run(testForR8Compat(parameters.getBackend()));
+ run(testForR8Compat(parameters.getBackend()), false);
}
@Test
public void testR8() throws Exception {
- run(testForR8(parameters.getBackend()));
+ run(testForR8(parameters.getBackend()), true);
}
@Test
public void testPG() throws Exception {
- run(testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass()));
+ run(testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass()), false);
}
- private TestRunResult<?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> builder) throws Exception {
+ private TestRunResult<?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> builder, boolean fullMode)
+ throws Exception {
return builder
.addInnerClasses(KeepClassMembersDefaultCtorTest.class)
.addKeepRules("-keepclassmembers class * { <fields>; }")
@@ -65,7 +67,12 @@
.run(parameters.getRuntime(), TestClass.class)
.inspectFailure(
inspector -> {
+ assertThat(inspector.clazz(A.class), isPresent());
assertThat(inspector.clazz(A.class).init(), isAbsent());
+ assertThat(inspector.clazz(A.class).uniqueFieldWithName("y"), isPresent());
+ assertThat(
+ inspector.clazz(A.class).uniqueFieldWithName("x"),
+ fullMode ? isAbsent() : isPresent());
})
.assertFailureWithErrorThatThrows(NoSuchMethodException.class);
}
@@ -73,6 +80,7 @@
static class A {
public long x = System.nanoTime();
+ public static long y = System.nanoTime();
public A() {
System.out.println("A()");
@@ -95,6 +103,7 @@
if (args.length > 0) {
// Use the field so we are sure that the keep rule triggers.
A a = getA();
+ System.out.println(a.y);
System.out.println(a.x);
}
}