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