[KeepAnno] Add builder option for experimental keep annotations.

Bug: b/248408342
Change-Id: I22c54b8b2d9f8eb2833d1bdb9c5c4f2ab6685d36
diff --git a/build.gradle b/build.gradle
index abf8ddf..de83e21 100644
--- a/build.gradle
+++ b/build.gradle
@@ -67,7 +67,7 @@
 sourceSets {
     main {
         java {
-            srcDirs = ['src/main/java']
+            srcDirs = ['src/main/java', 'src/keepanno/java']
         }
         resources {
             srcDirs "third_party/api_database/api_database"
@@ -75,7 +75,7 @@
     }
     main11 {
         java {
-            srcDirs = ['src/main/java']
+            srcDirs = ['src/main/java', 'src/keepanno/java']
         }
         resources {
             srcDirs "third_party/api_database/api_database"
@@ -83,7 +83,7 @@
     }
     main17 {
         java {
-            srcDirs = ['src/main/java']
+            srcDirs = ['src/main/java', 'src/keepanno/java']
         }
         resources {
             srcDirs "third_party/api_database/api_database"
@@ -213,7 +213,7 @@
 idea {
     sourceSets.all { SourceSet sources ->
         module {
-            if (sources.name == "main" || sources.name == "keepanno") {
+            if (sources.name == "main") {
                 sourceDirs += sources.java.srcDirs
                 outputDir sources.output.classesDirs[0]
             } else {
@@ -320,8 +320,6 @@
 
     keepannoCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
     keepannoCompile "com.google.guava:guava:$guavaVersion"
-    testCompile sourceSets.keepanno.output
-    testRuntime sourceSets.keepanno.output
 }
 
 def r8LibPath = "$buildDir/libs/r8lib.jar"
@@ -1106,7 +1104,6 @@
 task testJarSources(type: Jar, dependsOn: [testClasses, buildLibraryDesugarConversions]) {
     archiveFileName = "r8testsbase.jar"
     from sourceSets.test.output
-    from sourceSets.keepanno.output
     // We only want to include tests that use R8 when generating keep rules for applymapping.
     include "com/android/tools/r8/**"
     include "android/**"
@@ -2247,6 +2244,7 @@
 
 test { task ->
 
+    dependsOn sourceSets.keepanno.output
     dependsOn buildLibraryDesugarConversions
     dependsOn getJarsFromSupportLibs
     // R8.jar is required for running bootstrap tests.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
index 8872494..37713a9 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
@@ -3,6 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.annotations;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
 /**
  * A binding of a keep item.
  *
@@ -12,6 +17,8 @@
  *
  * <p>See KeepTarget for documentation on specifying an item pattern.
  */
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.CLASS)
 public @interface KeepBinding {
 
   /** Name with which other bindings, conditions or targets can reference the bound item pattern. */
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
index c88085a..f37c3fc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.annotations;
 
+
 /**
  * Utility class for referencing the various keep annotations and their structure.
  *
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 f3e97fa..515becd 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
@@ -29,11 +29,66 @@
 
 public class KeepEdgeWriter implements Opcodes {
 
+  /** Annotation visitor interface to allow usage from tests without type conflicts in r8lib. */
+  public interface AnnotationVisitorInterface {
+    int version();
+
+    void visit(String name, Object value);
+
+    void visitEnum(String name, String descriptor, String value);
+
+    AnnotationVisitorInterface visitAnnotation(String name, String descriptor);
+
+    AnnotationVisitorInterface visitArray(String name);
+
+    void visitEnd();
+  }
+
+  private static AnnotationVisitor wrap(AnnotationVisitorInterface visitor) {
+    if (visitor == null) {
+      return null;
+    }
+    return new AnnotationVisitor(visitor.version()) {
+
+      @Override
+      public void visit(String name, Object value) {
+        visitor.visit(name, value);
+      }
+
+      @Override
+      public void visitEnum(String name, String descriptor, String value) {
+        visitor.visitEnum(name, descriptor, value);
+      }
+
+      @Override
+      public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+        AnnotationVisitorInterface v = visitor.visitAnnotation(name, descriptor);
+        return v == visitor ? this : wrap(v);
+      }
+
+      @Override
+      public AnnotationVisitor visitArray(String name) {
+        AnnotationVisitorInterface v = visitor.visitArray(name);
+        return v == visitor ? this : wrap(v);
+      }
+
+      @Override
+      public void visitEnd() {
+        visitor.visitEnd();
+      }
+    };
+  }
+
   public static void writeEdge(KeepEdge edge, ClassVisitor visitor) {
-    writeEdge(edge, visitor::visitAnnotation);
+    writeEdgeInternal(edge, visitor::visitAnnotation);
   }
 
   public static void writeEdge(
+      KeepEdge edge, BiFunction<String, Boolean, AnnotationVisitorInterface> getVisitor) {
+    writeEdgeInternal(edge, (descriptor, visible) -> wrap(getVisitor.apply(descriptor, visible)));
+  }
+
+  public static void writeEdgeInternal(
       KeepEdge edge, BiFunction<String, Boolean, AnnotationVisitor> getVisitor) {
     new KeepEdgeWriter().writeEdge(edge, getVisitor.apply(Edge.DESCRIPTOR, false));
   }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 0bf09ac..d017a2f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -15,6 +15,9 @@
 import com.android.tools.r8.inspector.Inspector;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
 import com.android.tools.r8.naming.SourceFileRewriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
@@ -53,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.List;
 import java.util.Optional;
@@ -122,6 +126,7 @@
     private final List<FeatureSplit> featureSplits = new ArrayList<>();
     private String synthesizedClassPrefix = "";
     private boolean enableMissingLibraryApiModeling = false;
+    private boolean enableExperimentalKeepAnnotations = false;
 
     private final ProguardConfigurationParserOptions.Builder parserOptionsBuilder =
         ProguardConfigurationParserOptions.builder().readEnvironment();
@@ -447,6 +452,12 @@
       return self();
     }
 
+    @Deprecated
+    public Builder setEnableExperimentalKeepAnnotations(boolean enable) {
+      this.enableExperimentalKeepAnnotations = enable;
+      return self();
+    }
+
     @Override
     protected InternalProgramOutputPathConsumer createProgramOutputConsumer(
         Path path,
@@ -578,6 +589,9 @@
         proguardConfigurationConsumerForTesting.accept(configurationBuilder);
       }
       amendWithRulesAndProvidersForInjarsAndMetaInf(reporter, parser);
+      // Extract out rules for keep annotations and amend the configuration.
+      // TODO(b/248408342): Remove this and parse annotations as part of R8 root-set & enqueuer.
+      extractKeepAnnotationRules(parser);
       ProguardConfiguration configuration = configurationBuilder.build();
       getAppBuilder().addFilteredLibraryArchives(configuration.getLibraryjars());
 
@@ -697,6 +711,32 @@
       }
     }
 
+    private void extractKeepAnnotationRules(ProguardConfigurationParser parser) {
+      if (!enableExperimentalKeepAnnotations) {
+        return;
+      }
+      try {
+        for (ProgramResourceProvider provider : getAppBuilder().getProgramResourceProviders()) {
+          for (ProgramResource resource : provider.getProgramResources()) {
+            if (resource.getKind() == Kind.CF) {
+              Set<KeepEdge> edges = KeepEdgeReader.readKeepEdges(resource.getBytes());
+              KeepRuleExtractor extractor =
+                  new KeepRuleExtractor(
+                      rule -> {
+                        ProguardConfigurationSourceStrings source =
+                            new ProguardConfigurationSourceStrings(
+                                Collections.singletonList(rule), null, resource.getOrigin());
+                        parser.parse(source);
+                      });
+              edges.forEach(extractor::extract);
+            }
+          }
+        }
+      } catch (ResourceException e) {
+        throw getAppBuilder().getReporter().fatalError(new ExceptionDiagnostic(e));
+      }
+    }
+
     // Internal for-testing method to add post-processors of the proguard configuration.
     void addProguardConfigurationConsumerForTesting(Consumer<ProguardConfiguration.Builder> c) {
       Consumer<ProguardConfiguration.Builder> oldConsumer = proguardConfigurationConsumerForTesting;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index bd09578..8e19b25 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -48,7 +48,9 @@
   }
 
   public void addInlinableFieldMatchingPrecondition(DexField field) {
-    inlinableFieldsInPrecondition.put(field, field);
+    if (inlinableFieldsInPrecondition != null) {
+      inlinableFieldsInPrecondition.put(field, field);
+    }
   }
 
   public Set<DexField> getAndClearInlinableFieldsMatchingPrecondition() {
diff --git a/src/main/keep.txt b/src/main/keep.txt
index 2213ce9..89105b4 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -51,3 +51,7 @@
 }
 # Test in this class is using the class name to fing the original .java file.
 -keep class com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringMethods
+
+# Keep everything in the annotations package of keepanno.
+# These are public API as they denote the supported annotations to be interpreted by R8.
+-keep class com.android.tools.r8.keepanno.annotations.** { *; }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 34f8a0b..c4cb889 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.keepanno.KeepEdgeAnnotationsTest;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.profile.art.ArtProfileConsumer;
 import com.android.tools.r8.profile.art.ArtProfileProvider;
@@ -713,6 +714,13 @@
     return self();
   }
 
+  public T enableExperimentalKeepAnnotations() throws IOException {
+    builder.addClasspathResourceProvider(
+        DirectoryClassFileProvider.fromDirectory(KeepEdgeAnnotationsTest.KEEP_ANNO_PATH));
+    builder.setEnableExperimentalKeepAnnotations(true);
+    return self();
+  }
+
   public T enableProguardTestOptions() {
     builder.setEnableTestProguardOptions();
     return self();
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
index 5f2237d..3c09bff 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
@@ -22,7 +22,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -54,11 +53,9 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
-    System.out.println(rules);
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutKeepAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .addKeepRuntimeVisibleAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -78,19 +75,6 @@
         UnusedAnno.class);
   }
 
-  public List<byte[]> getInputClassesWithoutKeepAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(Base.class), isPresent());
 
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepBindingTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepBindingTest.java
index c83340f..cd00198 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepBindingTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepBindingTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,10 +49,9 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepClassRules(A.class, B.class)
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
@@ -64,10 +62,9 @@
 
   @Test
   public void testWithRuleExtractionAndNoKeepOnClass() throws Exception {
-    List<String> rules = getExtractedKeepRules();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
@@ -79,19 +76,6 @@
     return ImmutableList.of(TestClass.class, A.class, B.class, C.class);
   }
 
-  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector, boolean expectB) {
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(A.class).uniqueMethodWithOriginalName("foo"), isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
index f99f3bf..f4411a6 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
 import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
 import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
+import com.android.tools.r8.keepanno.asm.KeepEdgeWriter.AnnotationVisitorInterface;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
 import com.android.tools.r8.keepanno.processor.KeepEdgeProcessor;
 import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
 import com.android.tools.r8.keepanno.testsource.KeepDependentFieldSource;
@@ -42,10 +42,13 @@
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.objectweb.asm.AnnotationVisitor;
 
+@Ignore("b/248408342: These test break on r8lib builds because of src&test using ASM classes.")
 @RunWith(Parameterized.class)
 public class KeepEdgeAnnotationsTest extends TestBase {
 
@@ -64,7 +67,7 @@
     }
   }
 
-  private static final Path KEEP_ANNO_PATH =
+  public static final Path KEEP_ANNO_PATH =
       Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
 
   private static List<Class<?>> getTestClasses() {
@@ -150,6 +153,46 @@
     return transformed;
   }
 
+  /** Wrapper to bridge ASM visitors when using the r8lib compiled version of the keepanno lib. */
+  private AnnotationVisitorInterface wrap(AnnotationVisitor visitor) {
+    if (visitor == null) {
+      return null;
+    }
+    return new AnnotationVisitorInterface() {
+      @Override
+      public int version() {
+        return KeepEdgeReader.ASM_VERSION;
+      }
+
+      @Override
+      public void visit(String name, Object value) {
+        visitor.visit(name, value);
+      }
+
+      @Override
+      public void visitEnum(String name, String descriptor, String value) {
+        visitor.visitEnum(name, descriptor, value);
+      }
+
+      @Override
+      public AnnotationVisitorInterface visitAnnotation(String name, String descriptor) {
+        AnnotationVisitor v = visitor.visitAnnotation(name, descriptor);
+        return v == visitor ? this : wrap(v);
+      }
+
+      @Override
+      public AnnotationVisitorInterface visitArray(String name) {
+        AnnotationVisitor v = visitor.visitArray(name);
+        return v == visitor ? this : wrap(v);
+      }
+
+      @Override
+      public void visitEnd() {
+        visitor.visitEnd();
+      }
+    };
+  }
+
   @Test
   public void testAsmReader() throws Exception {
     assumeTrue(parameters.isCfRuntime());
@@ -169,7 +212,8 @@
                   @Override
                   public void visitEnd() {
                     for (KeepEdge edge : expectedEdges) {
-                      KeepEdgeWriter.writeEdge(edge, super::visitAnnotation);
+                      KeepEdgeWriter.writeEdge(
+                          edge, (desc, visible) -> wrap(super.visitAnnotation(desc, visible)));
                     }
                     super.visitEnd();
                   }
@@ -190,25 +234,15 @@
 
   @Test
   public void testExtractAndRun() throws Exception {
-    List<String> rules = getKeepRulesForClass(source);
     testForR8(parameters.getBackend())
-        .addClasspathFiles(KEEP_ANNO_PATH)
+        .enableExperimentalKeepAnnotations()
         .addProgramClassesAndInnerClasses(source)
-        .addKeepRules(rules)
         .addKeepMainRule(source)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), source)
         .assertSuccessWithOutput(getExpected());
   }
 
-  public static List<String> getKeepRulesForClass(Class<?> clazz) throws IOException {
-    Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
-    List<String> rules = new ArrayList<>();
-    KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
-    keepEdges.forEach(extractor::extract);
-    return rules;
-  }
-
   private void checkSynthesizedKeepEdgeClass(CodeInspector inspector, Path data)
       throws IOException {
     String synthesizedEdgesClassName =
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarAnyClassTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarAnyClassTest.java
index 74da176..82feaed 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarAnyClassTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarAnyClassTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,10 +47,9 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
@@ -63,19 +61,6 @@
     return ImmutableList.of(TestClass.class, A.class, B.class);
   }
 
-  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(A.class).uniqueMethodWithOriginalName("foo"), isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java
index 922a3ce..f1f1f6b 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,10 +49,9 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
@@ -64,20 +62,6 @@
   public List<Class<?>> getInputClasses() {
     return ImmutableList.of(TestClass.class, A.class, B.class);
   }
-
-  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(A.class).uniqueMethodWithOriginalName("foo"), isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
index 6f95aa6..a9c7405 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
@@ -11,10 +11,18 @@
 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.keepanno.annotations.KeepOption;
 import com.android.tools.r8.keepanno.annotations.KeepTarget;
 import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
 import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.function.ThrowingRunnable;
@@ -33,21 +41,30 @@
     parameters.assertNoneRuntime();
   }
 
+  private static List<String> extractRuleForClass(Class<?> clazz) throws IOException {
+    Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
+    List<String> rules = new ArrayList<>();
+    KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
+    keepEdges.forEach(extractor::extract);
+    return rules;
+  }
+
   private void assertThrowsWith(ThrowingRunnable fn, Matcher<String> matcher) {
     try {
       fn.run();
-      fail("Expected run to fail");
     } catch (KeepEdgeException e) {
       assertThat(e.getMessage(), matcher);
+      return;
     } catch (Throwable e) {
-      fail("Expected run to fail with KeepEdgeException");
+      fail("Expected run to fail with KeepEdgeException, but failed with: " + e);
     }
+    fail("Expected run to fail");
   }
 
   @Test
   public void testInvalidClassDecl() throws Exception {
     assertThrowsWith(
-        () -> KeepEdgeAnnotationsTest.getKeepRulesForClass(MultipleClassDeclarations.class),
+        () -> extractRuleForClass(MultipleClassDeclarations.class),
         allOf(
             containsString("Multiple declarations"),
             containsString("className"),
@@ -65,7 +82,7 @@
   @Test
   public void testInvalidExtendsDecl() throws Exception {
     assertThrowsWith(
-        () -> KeepEdgeAnnotationsTest.getKeepRulesForClass(MultipleExtendsDeclarations.class),
+        () -> extractRuleForClass(MultipleExtendsDeclarations.class),
         allOf(
             containsString("Multiple declarations"),
             containsString("extendsClassName"),
@@ -86,7 +103,7 @@
   @Test
   public void testInvalidMemberDecl() throws Exception {
     assertThrowsWith(
-        () -> KeepEdgeAnnotationsTest.getKeepRulesForClass(MultipleMemberDeclarations.class),
+        () -> extractRuleForClass(MultipleMemberDeclarations.class),
         allOf(containsString("field"), containsString("method")));
   }
 
@@ -101,7 +118,7 @@
   @Test
   public void testInvalidOptionsDecl() throws Exception {
     assertThrowsWith(
-        () -> KeepEdgeAnnotationsTest.getKeepRulesForClass(MultipleOptionDeclarations.class),
+        () -> extractRuleForClass(MultipleOptionDeclarations.class),
         allOf(containsString("options"), containsString("allow"), containsString("disallow")));
   }
 
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
index f4060f3..0bf08bc 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,10 +50,9 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
         // The "all members" target will create an unused "all fields" rule.
@@ -68,19 +66,6 @@
     return ImmutableList.of(TestClass.class, A.class);
   }
 
-  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector) throws Exception {
     assertThat(inspector.clazz(A.class).method(A.class.getMethod("foo")), isPresent());
     // TODO(b/265892343): The extracted rule will match all params so this is incorrectly kept.
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
index 02e86ae..8e78dda 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,13 +48,9 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
-    for (String rule : rules) {
-      System.out.println(rule);
-    }
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
         .allowUnusedProguardConfigurationRules()
@@ -68,19 +63,6 @@
     return ImmutableList.of(TestClass.class, A.class, B.class, C.class);
   }
 
-  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(B.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
index d692791..39eea01 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,10 +47,9 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
         .allowUnusedProguardConfigurationRules()
@@ -64,19 +62,6 @@
     return ImmutableList.of(TestClass.class, A.class, B.class, C.class);
   }
 
-  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(B.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java
index a9dc1f7..05c742a 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,10 +47,9 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
@@ -63,19 +61,6 @@
     return ImmutableList.of(TestClass.class, A.class, B.class);
   }
 
-  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(B.class), isPresent());
     assertThat(inspector.clazz(B.class).uniqueMethodWithOriginalName("<init>"), isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
index bfc7fb5..5177150 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,10 +47,9 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
@@ -63,19 +61,6 @@
     return ImmutableList.of(TestClass.class, A.class, B.class);
   }
 
-  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("classNameForB"), isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
index ee605b0..ace719a 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -19,7 +18,6 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Field;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,15 +49,18 @@
 
   @Test
   public void testWithRuleExtraction() throws Exception {
-    List<String> rules = getExtractedKeepRules();
-    assertEquals(1, rules.size());
-    assertThat(rules.get(0), containsString("context: " + descriptor(A.class) + "foo()V"));
-    assertThat(rules.get(0), containsString("description: Keep the\\nstring-valued fields"));
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(getInputClassesWithoutAnnotations())
-        .addKeepRules(rules)
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
+        .compile()
+        .apply(
+            c -> {
+              String rules = c.getProguardConfiguration().getParsedConfiguration();
+              assertThat(rules, containsString("context: " + descriptor(A.class) + "foo()V"));
+              assertThat(rules, containsString("description: Keep the\\nstring-valued fields"));
+            })
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
         .inspect(this::checkOutput);
@@ -69,19 +70,6 @@
     return ImmutableList.of(TestClass.class, A.class);
   }
 
-  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
-    return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
-  }
-
-  public List<String> getExtractedKeepRules() throws Exception {
-    List<Class<?>> classes = getInputClasses();
-    List<String> rules = new ArrayList<>();
-    for (Class<?> clazz : classes) {
-      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
-    }
-    return rules;
-  }
-
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("fieldA"), isPresent());