Add flags to control handling annotations in maindex computation
If the system property
com.android.tools.r8.ignoreBootClasspathEnumsForMaindexTracing
is set the maindex computation will not include enums from the
boot classpath when considering if an annotations has enum fields.
If the system property
com.android.tools.r8.pruneNonVissibleAnnotationClasses
is set all annotation classes with retention SOURCE or CLASS will
be pruned from the program.
Bug: 202173862
Change-Id: I1b90100662e6976297f1ce101d72d7a6c5b26b6b
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index e469f0a..07e0617 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
import com.android.tools.r8.graph.ApplicationReaderMap;
import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexApplicationReadFlags;
import com.android.tools.r8.graph.DexClass;
@@ -379,6 +380,20 @@
}
}
+ private boolean includeAnnotationClass(DexProgramClass clazz) {
+ if (!options.pruneNonVissibleAnnotationClasses) {
+ return true;
+ }
+ DexAnnotation retentionAnnotation =
+ clazz.annotations().getFirstMatching(itemFactory.retentionType);
+ // Default is CLASS retention, read if retained.
+ if (retentionAnnotation == null) {
+ return DexAnnotation.retainCompileTimeAnnotation(clazz.getType(), application.options);
+ }
+ // Otherwise only read runtime visible annotations.
+ return retentionAnnotation.annotation.toString().contains("RUNTIME");
+ }
+
private void readClassSources(
List<ProgramResource> classSources, Queue<DexProgramClass> classes) {
if (classSources.isEmpty()) {
@@ -386,7 +401,15 @@
}
hasReadProgramResourceFromCf = true;
JarClassFileReader<DexProgramClass> reader =
- new JarClassFileReader<>(application, classes::add, PROGRAM);
+ new JarClassFileReader<>(
+ application,
+ clazz -> {
+ if (clazz.isAnnotation() && !includeAnnotationClass(clazz)) {
+ return;
+ }
+ classes.add(clazz);
+ },
+ PROGRAM);
// Read classes in parallel.
for (ProgramResource input : classSources) {
futures.add(
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index 4952830..b648635 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -128,15 +128,18 @@
if (proto.parameters.isEmpty()) {
DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory());
if (valueType.isClassType()) {
- if (isEnum(valueType)) {
- value = true;
- break;
- } else if (isAnnotation(valueType) && isAnnotationWithEnum(valueType)) {
- value = true;
- break;
- }
+ assert !value;
+ boolean notLibraryOrTakeBootClasspath =
+ !appInfo().definitionFor(valueType).isLibraryClass()
+ || !appView.options().ignoreBootClasspathEnumsForMaindexTracing;
+ value =
+ (isEnum(valueType) && notLibraryOrTakeBootClasspath)
+ || (isAnnotation(valueType) && isAnnotationWithEnum(valueType));
}
}
+ if (value) {
+ break;
+ }
}
}
annotationTypeContainEnum.put(dexType, value);
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 1899f46..0fdc11c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -519,6 +519,10 @@
// Intermediate builds also emits or update synthesized classes mapping.
public boolean intermediate = false;
public boolean retainCompileTimeAnnotations = true;
+ public boolean ignoreBootClasspathEnumsForMaindexTracing =
+ System.getProperty("com.android.tools.r8.ignoreBootClasspathEnumsForMaindexTracing") != null;
+ public boolean pruneNonVissibleAnnotationClasses =
+ System.getProperty("com.android.tools.r8.pruneNonVissibleAnnotationClasses") != null;
public List<String> logArgumentsFilter = ImmutableList.of();
// Flag to turn on/offLoad/store optimization in the Cf back-end.
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexRuntimeAndProgramEnumInAnnotationTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexRuntimeAndProgramEnumInAnnotationTest.java
new file mode 100644
index 0000000..9d5f2f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexRuntimeAndProgramEnumInAnnotationTest.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2021, 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.maindexlist;
+
+import static com.android.tools.r8.maindexlist.MainDexRuntimeAndProgramEnumInAnnotationTest.EnumForAnnotation.TEST;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MainDexRuntimeAndProgramEnumInAnnotationTest extends TestBase {
+
+ Set<Class<?>> CUSTOM_CLASSES =
+ ImmutableSet.of(
+ B.class,
+ Main.class,
+ EnumForAnnotation.class,
+ RuntimeRetentionAnnotationWithProgramEnum.class);
+ Set<Class<?>> DEFAULT_CLASSES =
+ Sets.union(
+ CUSTOM_CLASSES,
+ ImmutableSet.of(
+ C.class,
+ RuntimeRetentionAnnotationWithRuntimeEnum.class,
+ RuntimeRetentionAnnotationWithoutEnum.class));
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean ignoreBootclasspathEnumsForMaindexTracing;
+
+ @Parameters(name = "{0}, ignoreBootclasspathEnumsForMaindexTracing: {1}")
+ public static List<Object[]> parameters() {
+ return buildParameters(
+ getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+ .build(),
+ BooleanUtils.values());
+ }
+
+ @Test
+ public void testMainDex() throws Exception {
+ testForMainDexListGenerator(temp)
+ .addInnerClasses(getClass())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addMainDexRules(
+ "-keep class " + Main.class.getTypeName() + " {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .applyIf(
+ ignoreBootclasspathEnumsForMaindexTracing,
+ builder ->
+ builder.addOptionsModification(
+ options -> {
+ options.ignoreBootClasspathEnumsForMaindexTracing = true;
+ }))
+ .run()
+ .inspectMainDexClasses(
+ mainDexList -> {
+ assertEquals(
+ (ignoreBootclasspathEnumsForMaindexTracing ? CUSTOM_CLASSES : DEFAULT_CLASSES)
+ .stream().map(Reference::classFromClass).collect(Collectors.toSet()),
+ new HashSet<>(mainDexList));
+ });
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(temp)
+ .addInnerClasses(getClass())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .setMinApi(parameters.getApiLevel())
+ .applyIf(
+ ignoreBootclasspathEnumsForMaindexTracing,
+ builder ->
+ builder.addOptionsModification(
+ options -> options.ignoreBootClasspathEnumsForMaindexTracing = true))
+ .collectMainDexClasses()
+ .addMainDexRules(
+ "-keep class " + Main.class.getTypeName() + " {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .compile()
+ .inspectMainDexClasses(
+ mainDexClasses -> {
+ assertEquals(
+ (ignoreBootclasspathEnumsForMaindexTracing ? CUSTOM_CLASSES : DEFAULT_CLASSES)
+ .stream().map(TestBase::typeName).collect(Collectors.toSet()),
+ mainDexClasses);
+ });
+ }
+
+ public enum EnumForAnnotation {
+ TEST
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface RuntimeRetentionAnnotationWithoutEnum {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface RuntimeRetentionAnnotationWithProgramEnum {
+
+ EnumForAnnotation value() default TEST;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface RuntimeRetentionAnnotationWithRuntimeEnum {
+
+ ElementType value();
+ }
+
+ @RuntimeRetentionAnnotationWithoutEnum
+ public static class A {
+
+ public static void main(String[] args) {}
+ }
+
+ @RuntimeRetentionAnnotationWithProgramEnum
+ public static class B {
+
+ public static void main(String[] args) {}
+ }
+
+ @RuntimeRetentionAnnotationWithRuntimeEnum(ElementType.TYPE)
+ public static class C {
+
+ public static void main(String[] args) {}
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexSourceAndClassRetentionTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexSourceAndClassRetentionTest.java
index 4bcb143..089f670 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexSourceAndClassRetentionTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexSourceAndClassRetentionTest.java
@@ -4,21 +4,20 @@
package com.android.tools.r8.maindexlist;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableSet;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
@@ -35,12 +34,17 @@
@Parameter(0)
public TestParameters parameters;
- @Parameters(name = "{0}")
- public static TestParametersCollection parameters() {
- return getTestParameters()
- .withDexRuntimes()
- .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
- .build();
+ @Parameter(1)
+ public boolean pruneNonVisibleAnnotationClasses;
+
+ @Parameters(name = "{0}, pruneNonVisibleAnnotationClasses: {1}")
+ public static List<Object[]> parameters() {
+ return buildParameters(
+ getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+ .build(),
+ BooleanUtils.values());
}
@Test
@@ -52,6 +56,12 @@
"-keep class " + Main.class.getTypeName() + " {",
" public static void main(java.lang.String[]);",
"}")
+ .applyIf(
+ pruneNonVisibleAnnotationClasses,
+ builder -> {
+ builder.addOptionsModification(
+ options -> options.pruneNonVissibleAnnotationClasses = true);
+ })
.run()
.inspectMainDexClasses(
mainDexList -> {
@@ -69,6 +79,12 @@
.addInnerClasses(getClass())
.addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
.setMinApi(parameters.getApiLevel())
+ .applyIf(
+ pruneNonVisibleAnnotationClasses,
+ builder -> {
+ builder.addOptionsModification(
+ options -> options.pruneNonVissibleAnnotationClasses = true);
+ })
.collectMainDexClasses()
.addMainDexRules(
"-keep class " + Main.class.getTypeName() + " {",
@@ -80,8 +96,12 @@
inspector -> {
// Source and class retention annotation classes are still in the output, but does not
// annotate anything.
- assertThat(inspector.clazz(SourceRetentionAnnotation.class), isPresent());
- assertThat(inspector.clazz(ClassRetentionAnnotation.class), isPresent());
+ assertEquals(
+ pruneNonVisibleAnnotationClasses,
+ !inspector.clazz(SourceRetentionAnnotation.class).isPresent());
+ assertEquals(
+ pruneNonVisibleAnnotationClasses,
+ !inspector.clazz(ClassRetentionAnnotation.class).isPresent());
assertEquals(0, inspector.clazz(Main.class).annotations().size());
assertEquals(0, inspector.clazz(A.class).annotations().size());
})