[KeepAnno] Make keep annotations available to the enqueuer

Bug: b/323816623
Change-Id: I986f2322dc3ab7f7d6eeb2a274eb9270e57d98db
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 01d57c7..85f651f 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -529,7 +529,7 @@
     }
   }
 
-  private static class ExtractedAnnotationsVisitor extends AnnotationVisitorBase {
+  public static class ExtractedAnnotationsVisitor extends AnnotationVisitorBase {
 
     private final Parent<KeepDeclaration> parent;
     private List<KeepDeclaration> declarations = new ArrayList<>();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index a0a98c7..7f8bdde 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.keepanno.ast.KeepAnnotationPattern;
 import com.android.tools.r8.keepanno.ast.KeepBindingReference;
 import com.android.tools.r8.keepanno.ast.KeepBindings;
+import com.android.tools.r8.keepanno.ast.KeepCheck;
 import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
@@ -149,26 +150,25 @@
     KeepEdgeMetaInfo metaInfo = decl.getMetaInfo();
     visitor.visit(ExtractedAnnotation.version, metaInfo.getVersion().toVersionString());
     visitor.visit(ExtractedAnnotation.context, metaInfo.getContextDescriptorString());
-    decl.match(
-        edge -> {
-          withNewVisitor(
-              visitor.visitAnnotation(ExtractedAnnotation.edge, Edge.DESCRIPTOR),
-              v -> new KeepEdgeWriter().writeEdge(edge, v));
-          return null;
-        },
-        check -> {
-          switch (check.getKind()) {
-            case REMOVED:
-              visitor.visit(ExtractedAnnotation.checkRemoved, true);
-              break;
-            case OPTIMIZED_OUT:
-              visitor.visit(ExtractedAnnotation.checkOptimizedOut, true);
-              break;
-            default:
-              throw new KeepEdgeException("Unexpected keep check kind: " + check.getKind());
-          }
-          return null;
-        });
+    if (decl.isKeepEdge()) {
+      KeepEdge edge = decl.asKeepEdge();
+      withNewVisitor(
+          visitor.visitAnnotation(ExtractedAnnotation.edge, Edge.DESCRIPTOR),
+          v -> new KeepEdgeWriter().writeEdge(edge, v));
+      return;
+    }
+    assert decl.isKeepCheck();
+    KeepCheck check = decl.asKeepCheck();
+    switch (check.getKind()) {
+      case REMOVED:
+        visitor.visit(ExtractedAnnotation.checkRemoved, true);
+        break;
+      case OPTIMIZED_OUT:
+        visitor.visit(ExtractedAnnotation.checkOptimizedOut, true);
+        break;
+      default:
+        throw new KeepEdgeException("Unexpected keep check kind: " + check.getKind());
+    }
   }
 
   private void writeEdge(KeepEdge edge, AnnotationVisitor visitor) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3e3c0ca..0b8ab6c 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -59,6 +59,7 @@
 import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.keepanno.ast.KeepDeclaration;
 import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.kotlin.KotlinMetadataUtils;
 import com.android.tools.r8.naming.IdentifierMinifier;
@@ -281,10 +282,12 @@
     timing.end();
     try {
       AppView<AppInfoWithClassHierarchy> appView;
+      List<KeepDeclaration> keepDeclarations;
       {
         timing.begin("Read app");
         ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
         LazyLoadedDexApplication lazyLoaded = applicationReader.read(executorService);
+        keepDeclarations = lazyLoaded.getKeepDeclarations();
         timing.begin("To direct app");
         DirectMappedDexApplication application = lazyLoaded.toDirect();
         timing.end();
@@ -389,7 +392,8 @@
                 appView,
                 profileCollectionAdditions,
                 subtypingInfo,
-                initialRuntimeTypeCheckInfoBuilder);
+                initialRuntimeTypeCheckInfoBuilder,
+                keepDeclarations);
         timing.end();
         timing.begin("After enqueuer");
         assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness);
@@ -1108,12 +1112,14 @@
       AppView<AppInfoWithClassHierarchy> appView,
       ProfileCollectionAdditions profileCollectionAdditions,
       SubtypingInfo subtypingInfo,
-      RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder)
+      RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder,
+      List<KeepDeclaration> keepDeclarations)
       throws ExecutionException {
     timing.begin("Set up enqueuer");
     Enqueuer enqueuer =
         EnqueuerFactory.createForInitialTreeShaking(
             appView, profileCollectionAdditions, executorService, subtypingInfo);
+    enqueuer.setKeepDeclarations(keepDeclarations);
     enqueuer.setAnnotationRemoverBuilder(annotationRemoverBuilder);
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
       enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
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 e3d2f78..04ff802 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -503,6 +503,9 @@
       if (libraryClassProvider != null) {
         builder.setLibraryClassCollection(new LibraryClassCollection(libraryClassProvider));
       }
+
+      // Transfer the keep declarations found during reading.
+      builder.setKeepDeclarations(application.getKeepDeclarations());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 2c4ce38..d6c5bdf 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -6,8 +6,10 @@
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
+import com.android.tools.r8.keepanno.ast.KeepDeclaration;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import org.objectweb.asm.Type;
@@ -27,6 +29,7 @@
   private final ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>();
   private final ApplicationReaderMap applicationReaderMap;
   private final DexApplicationReadFlags.Builder readFlagsBuilder;
+  private final List<KeepDeclaration> keepDeclarations = new ArrayList<>();
 
   public JarApplicationReader(
       InternalOptions options, DexApplicationReadFlags.Builder readFlagsBuilder) {
@@ -39,6 +42,16 @@
     this(options, DexApplicationReadFlags.builder());
   }
 
+  public void addKeepDeclaration(KeepDeclaration declaration) {
+    synchronized (keepDeclarations) {
+      keepDeclarations.add(declaration);
+    }
+  }
+
+  public List<KeepDeclaration> getKeepDeclarations() {
+    return keepDeclarations;
+  }
+
   public Type getAsmObjectType(String name) {
     return asmObjectTypeCache.computeIfAbsent(name, Type::getObjectType);
   }
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 05c265a..978932d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -35,6 +35,9 @@
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.keepanno.asm.KeepEdgeReader.ExtractedAnnotationsVisitor;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ExtractedAnnotations;
+import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticMarker;
 import com.android.tools.r8.utils.AsmUtils;
@@ -452,6 +455,17 @@
 
     @Override
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      if (!visible && ExtractedAnnotations.DESCRIPTOR.equals(desc)) {
+        if (!application.options.testing.enableExtractedKeepAnnotations) {
+          return null;
+        }
+        if (classKind != ClassKind.PROGRAM) {
+          return null;
+        }
+        return new ExtractedAnnotationsVisitor(
+            new ClassParsingContext(type.getName()).annotation(desc),
+            application::addKeepDeclaration);
+      }
       return createAnnotationVisitor(
           desc, visible, getAnnotations(), application, DexAnnotation::new);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 897d908..0fc1f96 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
+import com.android.tools.r8.keepanno.ast.KeepDeclaration;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ClasspathClassCollection;
 import com.android.tools.r8.utils.InternalOptions;
@@ -17,6 +18,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -30,6 +32,7 @@
   private final ProgramClassCollection programClasses;
   private final ClasspathClassCollection classpathClasses;
   private final LibraryClassCollection libraryClasses;
+  private final List<KeepDeclaration> keepDeclarations;
 
   /** Constructor should only be invoked by the DexApplication.Builder. */
   private LazyLoadedDexApplication(
@@ -39,12 +42,18 @@
       ImmutableList<DataResourceProvider> dataResourceProviders,
       ClasspathClassCollection classpathClasses,
       LibraryClassCollection libraryClasses,
+      List<KeepDeclaration> keepDeclarations,
       InternalOptions options,
       Timing timing) {
     super(proguardMap, flags, dataResourceProviders, options, timing);
     this.programClasses = programClasses;
     this.classpathClasses = classpathClasses;
     this.libraryClasses = libraryClasses;
+    this.keepDeclarations = keepDeclarations;
+  }
+
+  public List<KeepDeclaration> getKeepDeclarations() {
+    return keepDeclarations;
   }
 
   @Override
@@ -252,6 +261,7 @@
 
     private ClasspathClassCollection classpathClasses;
     private LibraryClassCollection libraryClasses;
+    private List<KeepDeclaration> keepDeclarations = Collections.emptyList();
 
     Builder(InternalOptions options, Timing timing) {
       super(options, timing);
@@ -280,6 +290,11 @@
       return this;
     }
 
+    public Builder setKeepDeclarations(List<KeepDeclaration> declarations) {
+      this.keepDeclarations = declarations;
+      return this;
+    }
+
     @Override
     public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) {
       addProgramClass(clazz);
@@ -300,6 +315,7 @@
           ImmutableList.copyOf(dataResourceProviders),
           classpathClasses,
           libraryClasses,
+          keepDeclarations,
           options,
           timing);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 34cb2ec..3fa01e0 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.features.IsolatedFeatureSplitsChecker;
@@ -129,6 +130,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
+import com.android.tools.r8.keepanno.ast.KeepDeclaration;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringTypeLookupResult;
@@ -289,6 +291,8 @@
 
   private final Set<DexMember<?, ?>> identifierNameStrings = Sets.newIdentityHashSet();
 
+  private List<KeepDeclaration> keepDeclarations = Collections.emptyList();
+
   /**
    * Tracks the dependency between a method and the super-method it calls, if any. Used to make
    * super methods become live when they become reachable from a live sub-method.
@@ -642,6 +646,13 @@
         .registerNewInstanceAnalysis(analysis);
   }
 
+  public void setKeepDeclarations(List<KeepDeclaration> keepDeclarations) {
+    // Keep declarations are used during initial tree shaking. Re-runs use the rule instance sets.
+    assert mode.isInitialTreeShaking();
+    assert keepDeclarations != null;
+    this.keepDeclarations = keepDeclarations;
+  }
+
   public void setAnnotationRemoverBuilder(AnnotationRemover.Builder annotationRemoverBuilder) {
     this.annotationRemoverBuilder = annotationRemoverBuilder;
   }
@@ -3700,6 +3711,9 @@
     rootSet.pendingMethodMoveInverse.forEach(pendingMethodMoveInverse::put);
     // Translate the result of root-set computation into enqueuer actions.
     timing.begin("Register analysis");
+    // TODO(b/323816623): This check does not include presence of keep declarations.
+    //  The non-presense of PG config seems like a exeedingly rare corner case so maybe just
+    //  make this conditional on tree shaking and the specific option flag.
     if (mode.isTreeShaking()
         && appView.options().hasProguardConfiguration()
         && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
@@ -3707,6 +3721,9 @@
           new KotlinMetadataEnqueuerExtension(
               appView, enqueuerDefinitionSupplier, initialPrunedTypes));
     }
+    // TODO(b/323816623): This check does not include presence of keep declarations.
+    //  We should consider if we should always run the signature analysis and just not emit them
+    //  in the end?
     if (appView.options().getProguardConfiguration() != null
         && appView.options().getProguardConfiguration().getKeepAttributes().signature) {
       registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
@@ -3722,6 +3739,10 @@
     timing.end();
 
     if (mode.isInitialTreeShaking()) {
+      // TODO(b/323816623): Start native interpretation here...
+      if (!keepDeclarations.isEmpty()) {
+        throw new Unimplemented("Native support for keep annotaitons pending");
+      }
       // Amend library methods with covariant return types.
       timing.begin("Model library");
       modelLibraryMethodsWithCovariantReturnTypes(appView);
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 c120ac1..f116aef 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2175,6 +2175,8 @@
 
   public static class TestingOptions {
 
+    public boolean enableExtractedKeepAnnotations = false;
+
     public boolean enableNumberUnboxer = false;
     public boolean printNumberUnboxed = false;
     public boolean roundtripThroughLir = false;