[KeepAnno] Introduce extraction options to support other shrinkers
Bug: b/321674067
Change-Id: I92fedbf06d10a3fa42a5da6cafc1931bc7c5b0db
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index 7149900..1c554fc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -49,10 +49,16 @@
/** Extract the PG keep rules that over-approximate a keep edge. */
public class KeepRuleExtractor {
+ private final KeepRuleExtractorOptions options;
private final Consumer<String> ruleConsumer;
public KeepRuleExtractor(Consumer<String> ruleConsumer) {
+ this(ruleConsumer, KeepRuleExtractorOptions.getR8Options());
+ }
+
+ public KeepRuleExtractor(Consumer<String> ruleConsumer, KeepRuleExtractorOptions options) {
this.ruleConsumer = ruleConsumer;
+ this.options = options;
}
public void extract(KeepDeclaration declaration) {
@@ -60,7 +66,7 @@
PgRule.groupByKinds(rules);
StringBuilder builder = new StringBuilder();
for (PgRule rule : rules) {
- rule.printRule(builder);
+ rule.printRule(builder, options);
builder.append("\n");
}
ruleConsumer.accept(builder.toString());
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorOptions.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorOptions.java
new file mode 100644
index 0000000..e428358
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorOptions.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2024, 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.keepanno.keeprules;
+
+import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
+
+public class KeepRuleExtractorOptions {
+
+ private static final KeepRuleExtractorOptions PG_OPTIONS = new KeepRuleExtractorOptions(false);
+ private static final KeepRuleExtractorOptions R8_OPTIONS = new KeepRuleExtractorOptions(true);
+
+ public static KeepRuleExtractorOptions getPgOptions() {
+ return PG_OPTIONS;
+ }
+
+ public static KeepRuleExtractorOptions getR8Options() {
+ return R8_OPTIONS;
+ }
+
+ private final boolean allowAccessModificationOption;
+ private final boolean allowAnnotationRemovalOption = false;
+
+ private KeepRuleExtractorOptions(boolean allowAccessModificationOption) {
+ this.allowAccessModificationOption = allowAccessModificationOption;
+ }
+
+ private boolean hasAllowAccessModificationOptionSupport() {
+ return allowAccessModificationOption;
+ }
+
+ private boolean hasAllowAnnotationRemovalOptionSupport() {
+ return allowAnnotationRemovalOption;
+ }
+
+ public boolean isKeepOptionSupported(KeepOption keepOption) {
+ switch (keepOption) {
+ case ACCESS_MODIFICATION:
+ return hasAllowAccessModificationOptionSupport();
+ case ANNOTATION_REMOVAL:
+ return hasAllowAnnotationRemovalOptionSupport();
+ default:
+ return true;
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
index c329c34..75ea20c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
@@ -114,14 +114,14 @@
};
}
- void printKeepOptions(StringBuilder builder) {
- RulePrintingUtils.printKeepOptions(builder, options);
+ void printKeepOptions(StringBuilder builder, KeepRuleExtractorOptions extractorOptions) {
+ RulePrintingUtils.printKeepOptions(builder, options, extractorOptions);
}
- public void printRule(StringBuilder builder) {
+ public void printRule(StringBuilder builder, KeepRuleExtractorOptions extractorOptions) {
RulePrintingUtils.printHeader(builder, metaInfo);
printCondition(builder);
- printConsequence(builder);
+ printConsequence(builder, extractorOptions);
}
void printCondition(StringBuilder builder) {
@@ -141,9 +141,9 @@
}
}
- void printConsequence(StringBuilder builder) {
+ void printConsequence(StringBuilder builder, KeepRuleExtractorOptions extractorOptions) {
builder.append(getConsequenceKeepType());
- printKeepOptions(builder);
+ printKeepOptions(builder, extractorOptions);
builder.append(' ');
printTargetHolder(builder);
List<KeepBindingSymbol> members = getTargetMembers();
@@ -249,7 +249,7 @@
}
@Override
- public void printRule(StringBuilder builder) {
+ public void printRule(StringBuilder builder, KeepRuleExtractorOptions options) {
RulePrintingUtils.printHeader(builder, getMetaInfo());
builder.append(getConsequenceKeepType()).append(" ");
List<KeepAttribute> sorted = new ArrayList<>(attributes);
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 2bda0ec..636dbfc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -84,13 +84,10 @@
return string;
}
- public static void printKeepOptions(StringBuilder builder, KeepOptions options) {
+ public static void printKeepOptions(
+ StringBuilder builder, KeepOptions options, KeepRuleExtractorOptions extractorOptions) {
for (KeepOption option : KeepOption.values()) {
- if (option == KeepOption.ANNOTATION_REMOVAL) {
- // Annotation removal is a testing option, we can't reliably extract it out into rules.
- continue;
- }
- if (options.isAllowed(option)) {
+ if (options.isAllowed(option) && extractorOptions.isKeepOptionSupported(option)) {
builder.append(",allow").append(getOptionString(option));
}
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
new file mode 100644
index 0000000..7f79344
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.ExternalR8TestBuilder;
+import com.android.tools.r8.ProguardTestBuilder;
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.keepanno.ast.KeepDeclaration;
+import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
+import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractorOptions;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+import org.junit.rules.TemporaryFolder;
+
+public class KeepAnnoTestUtils {
+
+ // TODO(b/321674067): Update this to PG 7.4.
+ public static ProguardVersion PG_VERSION = ProguardVersion.V7_3_2;
+
+ // TODO(b/321674067): Downgrade this to oldest supported AGP, such as R8 8.0.35.
+ private static Path R8_LIB = Paths.get(ToolHelper.THIRD_PARTY_DIR, "r8", "r8lib_8.2.20-dev.jar");
+
+ public static Path getKeepAnnoLib(TemporaryFolder temp) throws IOException {
+ Path archive = temp.newFolder().toPath().resolve("keepanno.jar");
+ Path root = ToolHelper.getKeepAnnoPath();
+ ArchiveConsumer consumer = new ArchiveConsumer(archive);
+ Path annoDir =
+ root.resolve(Paths.get("com", "android", "tools", "r8", "keepanno", "annotations"));
+ try (Stream<Path> paths = Files.list(annoDir)) {
+ paths.forEach(
+ p -> {
+ if (FileUtils.isClassFile(p)) {
+ byte[] data = FileUtils.uncheckedReadAllBytes(p);
+ String fileName = p.getFileName().toString();
+ String className = fileName.substring(0, fileName.lastIndexOf('.'));
+ String desc = "Lcom/android/tools/r8/keepanno/annotations/" + className + ";";
+ consumer.accept(ByteDataView.of(data), desc, null);
+ }
+ });
+ }
+ consumer.finished(null);
+ return archive;
+ }
+
+ public static List<String> extractRules(
+ List<Class<?>> inputClasses, KeepRuleExtractorOptions extractorOptions) throws IOException {
+ List<String> rules = new ArrayList<>();
+ for (Class<?> inputClass : inputClasses) {
+ byte[] bytes = ToolHelper.getClassAsBytes(inputClass);
+ List<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(bytes);
+ KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add, extractorOptions);
+ declarations.forEach(extractor::extract);
+ }
+ return rules;
+ }
+
+ public static ThrowableConsumer<ProguardTestBuilder> addInputClassesAndRulesPG(
+ List<Class<?>> inputClasses) {
+ return builder -> {
+ addInputClassesAndRulesShared(inputClasses, builder);
+ };
+ }
+
+ public static ThrowableConsumer<ExternalR8TestBuilder> addInputClassesAndRulesR8(
+ List<Class<?>> inputClasses) {
+ return builder -> {
+ builder.useProvidedR8(R8_LIB);
+ addInputClassesAndRulesShared(inputClasses, builder);
+ };
+ }
+
+ private static void addInputClassesAndRulesShared(
+ List<Class<?>> inputClasses, TestShrinkerBuilder<?, ?, ?, ?, ?> builder) throws IOException {
+ Path keepAnnoLib = getKeepAnnoLib(builder.getState().getTempFolder());
+ List<String> rules = extractRules(inputClasses, KeepRuleExtractorOptions.getPgOptions());
+ builder.addProgramClasses(inputClasses).addProgramFiles(keepAnnoLib).addKeepRules(rules);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java
index 210cea0..e6edebc 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java
@@ -6,7 +6,9 @@
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 static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.ProguardVersion;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -60,6 +62,30 @@
}
@Test
+ public void testExtractR8() throws Exception {
+ testForExternalR8(parameters.getBackend())
+ .apply(KeepAnnoTestUtils.addInputClassesAndRulesR8(getInputClasses()))
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ @Test
+ public void testExtractPG() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForProguard(KeepAnnoTestUtils.PG_VERSION)
+ .addDontWarn(getClass())
+ .apply(KeepAnnoTestUtils.addInputClassesAndRulesPG(getInputClasses()))
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ @Test
public void testNoRefReference() throws Exception {
testForRuntime(parameters)
.addProgramClasses(getInputClasses())
@@ -80,6 +106,31 @@
.inspect(this::checkOutputNoRef);
}
+ @Test
+ public void testNoRefExtractR8() throws Exception {
+ testForExternalR8(parameters.getBackend())
+ .apply(KeepAnnoTestUtils.addInputClassesAndRulesR8(getInputClasses()))
+ .addKeepMainRule(TestClassNoRef.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClassNoRef.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutputNoRef);
+ }
+
+ @Test
+ public void testNoRefExtractPG() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForProguard(ProguardVersion.V7_3_2)
+ .addDontWarn(getClass())
+ .apply(KeepAnnoTestUtils.addInputClassesAndRulesPG(getInputClasses()))
+ .addKeepMainRule(TestClassNoRef.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClassNoRef.class)
+ .assertSuccessWithOutput(EXPECTED)
+ // PG does not eliminate B so the same output remains.
+ .inspect(this::checkOutput);
+ }
+
public List<Class<?>> getInputClasses() {
return ImmutableList.of(TestClass.class, TestClassNoRef.class, A.class, B.class, C.class);
}