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()); })