Prune class-retention attributes from DEX inputs.

Bug: 130028992
Change-Id: Ia15d0ecef58511f564d1a96454cf1eb201aaf8ee
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 3c239c8..315c217 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -228,7 +228,7 @@
           if (options.passthroughDexCode) {
             computedMinApiLevel = validateOrComputeMinApiLevel(computedMinApiLevel, dexReader);
           }
-          dexParsers.add(new DexParser(dexReader, classKind, itemFactory, options.reporter));
+          dexParsers.add(new DexParser(dexReader, classKind, options));
         }
         options.minApiLevel = computedMinApiLevel;
         for (DexParser dexParser : dexParsers) {
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 1760ca4..3ef485f 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.utils.EncodedValueUtils.parseSigned;
 import static com.android.tools.r8.utils.EncodedValueUtils.parseUnsigned;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InstructionFactory;
@@ -59,6 +58,7 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.io.ByteStreams;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
@@ -85,7 +85,7 @@
   private final DexSection[] dexSections;
   private int[] stringIDs;
   private final ClassKind classKind;
-  private final DiagnosticsHandler reporter;
+  private final InternalOptions options;
 
   public static DexSection[] parseMapFrom(Path file) throws IOException {
     return parseMapFrom(Files.newInputStream(file), new PathOrigin(file));
@@ -96,9 +96,7 @@
   }
 
   private static DexSection[] parseMapFrom(DexReader dexReader) {
-    DexParser dexParser =
-        new DexParser(
-            dexReader, ClassKind.PROGRAM, new DexItemFactory(), new DiagnosticsHandler() {});
+    DexParser dexParser = new DexParser(dexReader, ClassKind.PROGRAM, new InternalOptions());
     return dexParser.dexSections;
   }
 
@@ -123,17 +121,16 @@
   // Factory to canonicalize certain dexitems.
   private final DexItemFactory dexItemFactory;
 
-  public DexParser(DexReader dexReader,
-      ClassKind classKind, DexItemFactory dexItemFactory, DiagnosticsHandler reporter) {
+  public DexParser(DexReader dexReader, ClassKind classKind, InternalOptions options) {
     assert dexReader.getOrigin() != null;
     this.origin = dexReader.getOrigin();
     this.dexReader = dexReader;
-    this.dexItemFactory = dexItemFactory;
+    this.dexItemFactory = options.itemFactory;
     dexReader.setByteOrder();
     dexSections = parseMap();
     parseStringIDs();
     this.classKind = classKind;
-    this.reporter = reporter;
+    this.options = options;
   }
 
   private void ensureCodesInited() {
@@ -426,8 +423,17 @@
       annotationOffsets[i] = dexReader.getUint();
     }
     DexAnnotation[] result = new DexAnnotation[size];
+    int actualSize = 0;
     for (int i = 0; i < size; i++) {
-      result[i] = annotationAt(annotationOffsets[i]);
+      DexAnnotation dexAnnotation = annotationAt(annotationOffsets[i]);
+      if (retainAnnotation(dexAnnotation)) {
+        result[actualSize++] = dexAnnotation;
+      }
+    }
+    if (actualSize < size) {
+      DexAnnotation[] temp = new DexAnnotation[actualSize];
+      System.arraycopy(result, 0, temp, 0, actualSize);
+      result = temp;
     }
     DexType dupType = DexAnnotationSet.findDuplicateEntryType(result);
     if (dupType != null) {
@@ -437,6 +443,11 @@
     return new DexAnnotationSet(result);
   }
 
+  private boolean retainAnnotation(DexAnnotation annotation) {
+    return annotation.visibility != DexAnnotation.VISIBILITY_BUILD
+        || DexAnnotation.retainCompileTimeAnnotation(annotation.annotation.type, options);
+  }
+
   private DexAnnotationSet annotationSetAt(int offset) {
     return (DexAnnotationSet) cacheAt(offset, this::parseAnnotationSet, DexAnnotationSet::empty);
   }
@@ -691,7 +702,7 @@
       }
 
       AttributesAndAnnotations attrs =
-          new AttributesAndAnnotations(type, annotationsDirectory.clazz, dexItemFactory);
+          new AttributesAndAnnotations(type, annotationsDirectory.clazz, options.itemFactory);
 
       DexClass clazz =
           classKind.create(
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 6fc1e1d..c3d914f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -67,6 +69,24 @@
     mixedItems.add(this);
   }
 
+  public static boolean retainCompileTimeAnnotation(DexType annotation, InternalOptions options) {
+    if (options.readCompileTimeAnnotations) {
+      return true;
+    }
+    if (annotation == options.itemFactory.dalvikFastNativeAnnotation
+        || annotation == options.itemFactory.dalvikCriticalNativeAnnotation
+        || annotation == options.itemFactory.annotationSynthesizedClassMap) {
+      return true;
+    }
+    if (options.processCovariantReturnTypeAnnotations) {
+      // @CovariantReturnType annotations are processed by CovariantReturnTypeAnnotationTransformer,
+      // they thus need to be read here and will then be removed as part of the processing.
+      return CovariantReturnTypeAnnotationTransformer.isCovariantReturnTypeAnnotation(
+          annotation, options.itemFactory);
+    }
+    return false;
+  }
+
   public static DexAnnotation createEnclosingClassAnnotation(DexType enclosingClass,
       DexItemFactory factory) {
     return createSystemValueAnnotation(factory.annotationEnclosingClass, factory,
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 16e9d44..87777e3 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -29,7 +29,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
-import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -168,21 +167,9 @@
 
   private static boolean retainCompileTimeAnnotation(
       String desc, JarApplicationReader application) {
-    if (application.options.readCompileTimeAnnotations) {
-      return true;
-    }
-    DexType type = application.getTypeFromDescriptor(desc);
-    if (type == application.options.itemFactory.dalvikFastNativeAnnotation
-        || type == application.options.itemFactory.dalvikCriticalNativeAnnotation) {
-      return true;
-    }
-    if (application.options.processCovariantReturnTypeAnnotations) {
-      // @CovariantReturnType annotations are processed by CovariantReturnTypeAnnotationTransformer,
-      // they thus need to be read here and will then be removed as part of the processing.
-      return CovariantReturnTypeAnnotationTransformer.isCovariantReturnTypeAnnotation(
-          type, application.options.itemFactory);
-    }
-    return false;
+    return application.options.readCompileTimeAnnotations
+        || DexAnnotation.retainCompileTimeAnnotation(
+            application.getTypeFromDescriptor(desc), application.options);
   }
 
   private static DexEncodedAnnotation createEncodedAnnotation(String desc,
diff --git a/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java b/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java
index be166ec..1509320 100644
--- a/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java
@@ -9,8 +9,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -43,7 +43,7 @@
         .flatMap(
             parameters -> {
               if (parameters.isCfRuntime()) {
-                return Stream.of((Object) new Object[] {parameters, false});
+                return Stream.of((Object) new Object[] {parameters, true});
               }
               return Stream.of(new Object[] {parameters, true}, new Object[] {parameters, false});
             })
@@ -81,26 +81,48 @@
 
   @Test
   public void test() throws Exception {
-    TestBuilder<?, ?> testBuilder =
-        parameters.isCfRuntime()
-            ? testForJvm()
-            : testForD8().setMinApi(parameters.getRuntime()).setIntermediate(intermediate);
+    if (parameters.isCfRuntime()) {
+      assertTrue(intermediate);
+      checkAnnotations(
+          testForJvm()
+              .addProgramClasses(CLASSES)
+              .run(parameters.getRuntime(), A.class)
+              .assertSuccessWithOutput(EXPECTED)
+              .inspector(),
+          intermediate);
+    } else {
+      D8TestCompileResult compile =
+          testForD8()
+              .setMinApi(parameters.getRuntime())
+              .setIntermediate(intermediate)
+              .addProgramClasses(CLASSES)
+              .compile();
+      checkAnnotations(
+          compile
+              .run(parameters.getRuntime(), A.class)
+              .assertSuccessWithOutput(EXPECTED)
+              .inspector(),
+          intermediate);
+      // If the first build was an intermediate, re-compile and check the final output.
+      if (intermediate) {
+        checkAnnotations(
+            testForD8()
+                .setMinApi(parameters.getRuntime())
+                .addProgramFiles(compile.writeToZip())
+                .run(parameters.getRuntime(), A.class)
+                .inspector(),
+            false);
+      }
+    }
+  }
 
-    CodeInspector inspector =
-        testBuilder
-            .addProgramClasses(CLASSES)
-            .run(parameters.getRuntime(), A.class)
-            .assertSuccessWithOutput(EXPECTED)
-            .inspector();
-
+  private static void checkAnnotations(CodeInspector inspector, boolean isClassRetained) {
     ClassSubject clazz = inspector.clazz(A.class);
     assertThat(clazz, isPresent());
     // Source retained annotations are always gone, even in the CF inputs.
     assertFalse(clazz.annotation(SourceRetained.class.getName()).isPresent());
     // Class retained annotations are present in CF and in intermediate builds.
-    assertEquals(
-        parameters.isCfRuntime() || intermediate,
-        clazz.annotation(ClassRetained.class.getName()).isPresent());
+    assertEquals(isClassRetained, clazz.annotation(ClassRetained.class.getName()).isPresent());
     // Runtime retained annotations are present in all.
     assertTrue(clazz.annotation(RuntimeRetained.class.getName()).isPresent());
   }