[KeepAnno] Add reading of non-extracted annotations in R8

This also extends the tests with a variant that will continue to extract out
keep rules with the current compiler version, so that we continue to track rule
interpretation.

Bug: b/323816623
Change-Id: I8cbf6b1a668fbb68aa8b92a35d41f73e0491d22e
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 85f651f..05f103b 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
@@ -89,6 +89,37 @@
 
   public static int ASM_VERSION = ASM9;
 
+  public static boolean isClassKeepAnnotation(String descriptor, boolean visible) {
+    return !visible && (isExtractedAnnotation(descriptor) || isEmbeddedAnnotation(descriptor));
+  }
+
+  public static boolean isFieldKeepAnnotation(String descriptor, boolean visible) {
+    return !visible && isEmbeddedAnnotation(descriptor);
+  }
+
+  public static boolean isMethodKeepAnnotation(String descriptor, boolean visible) {
+    return !visible && isEmbeddedAnnotation(descriptor);
+  }
+
+  private static boolean isExtractedAnnotation(String descriptor) {
+    return ExtractedAnnotations.DESCRIPTOR.equals(descriptor);
+  }
+
+  private static boolean isEmbeddedAnnotation(String descriptor) {
+    switch (descriptor) {
+      case AnnotationConstants.Edge.DESCRIPTOR:
+      case AnnotationConstants.UsesReflection.DESCRIPTOR:
+      case AnnotationConstants.ForApi.DESCRIPTOR:
+      case AnnotationConstants.UsedByReflection.DESCRIPTOR:
+      case AnnotationConstants.UsedByNative.DESCRIPTOR:
+      case AnnotationConstants.CheckRemoved.DESCRIPTOR:
+      case AnnotationConstants.CheckOptimizedOut.DESCRIPTOR:
+        return true;
+      default:
+        return false;
+    }
+  }
+
   public static List<KeepDeclaration> readKeepEdges(byte[] classFileBytes) {
     return internalReadKeepEdges(classFileBytes, true, false);
   }
@@ -107,6 +138,84 @@
     return declarations;
   }
 
+  public static AnnotationVisitor createClassKeepAnnotationVisitor(
+      String descriptor,
+      boolean visible,
+      boolean readEmbedded,
+      boolean readExtracted,
+      String className,
+      AnnotationParsingContext parsingContext,
+      Consumer<KeepDeclaration> callback) {
+    return KeepEdgeClassVisitor.createAnnotationVisitor(
+        descriptor,
+        visible,
+        readEmbedded,
+        readExtracted,
+        callback,
+        parsingContext,
+        className,
+        builder -> {
+          builder.setContextFromClassDescriptor(
+              KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className));
+        });
+  }
+
+  public static AnnotationVisitor createFieldKeepAnnotationVisitor(
+      String descriptor,
+      boolean visible,
+      boolean readEmbedded,
+      boolean readExtracted,
+      String className,
+      String fieldName,
+      String fieldTypeDescriptor,
+      AnnotationParsingContext parsingContext,
+      Consumer<KeepDeclaration> callback) {
+    return KeepEdgeFieldVisitor.createAnnotationVisitor(
+        descriptor,
+        visible,
+        readEmbedded,
+        readExtracted,
+        callback::accept,
+        parsingContext,
+        className,
+        fieldName,
+        fieldTypeDescriptor,
+        builder -> {
+          builder.setContextFromFieldDescriptor(
+              KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className),
+              fieldName,
+              fieldTypeDescriptor);
+        });
+  }
+
+  public static AnnotationVisitor createMethodKeepAnnotationVisitor(
+      String descriptor,
+      boolean visible,
+      boolean readEmbedded,
+      boolean readExtracted,
+      String className,
+      String methodName,
+      String methodDescriptor,
+      AnnotationParsingContext parsingContext,
+      Consumer<KeepDeclaration> callback) {
+    return KeepEdgeMethodVisitor.createAnnotationVisitor(
+        descriptor,
+        visible,
+        readEmbedded,
+        readExtracted,
+        callback::accept,
+        parsingContext,
+        className,
+        methodName,
+        methodDescriptor,
+        (KeepEdgeMetaInfo.Builder builder) -> {
+          builder.setContextFromMethodDescriptor(
+              KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className),
+              methodName,
+              methodDescriptor);
+        });
+  }
+
   private static KeepClassItemReference classReferenceFromName(String className) {
     return KeepClassItemReference.fromClassNamePattern(
         KeepQualifiedClassNamePattern.exact(className));
@@ -223,7 +332,7 @@
         String[] interfaces) {
       super.visit(version, access, name, signature, superName, interfaces);
       className = binaryNameToTypeName(name);
-      parsingContext = new ClassParsingContext(className);
+      parsingContext = ClassParsingContext.fromName(className);
     }
 
     private AnnotationParsingContext annotationParsingContext(String descriptor) {
@@ -232,56 +341,62 @@
 
     @Override
     public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+      return createAnnotationVisitor(
+          descriptor,
+          visible,
+          readEmbedded,
+          readExtracted,
+          parent::accept,
+          annotationParsingContext(descriptor),
+          className,
+          this::setContext);
+    }
+
+    private static AnnotationVisitorBase createAnnotationVisitor(
+        String descriptor,
+        boolean visible,
+        boolean readEmbedded,
+        boolean readExtracted,
+        Consumer<KeepDeclaration> parent,
+        AnnotationParsingContext parsingContext,
+        String className,
+        Consumer<KeepEdgeMetaInfo.Builder> setContext) {
       // Skip any visible annotations as @KeepEdge is not runtime visible.
       if (visible) {
         return null;
       }
-      if (readExtracted && descriptor.equals(ExtractedAnnotations.DESCRIPTOR)) {
-        return new ExtractedAnnotationsVisitor(
-            annotationParsingContext(descriptor), parent::accept);
+
+      if (readExtracted && isExtractedAnnotation(descriptor)) {
+        return new ExtractedAnnotationsVisitor(parsingContext, parent::accept);
       }
-      if (!readEmbedded) {
+      if (!readEmbedded || !isEmbeddedAnnotation(descriptor)) {
         return null;
       }
       if (descriptor.equals(Edge.DESCRIPTOR)) {
-        return new KeepEdgeVisitor(
-            annotationParsingContext(descriptor), parent::accept, this::setContext);
+        return new KeepEdgeVisitor(parsingContext, parent::accept, setContext);
       }
       if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
         KeepClassItemPattern classItem =
             KeepClassItemPattern.builder()
                 .setClassNamePattern(KeepQualifiedClassNamePattern.exact(className))
                 .build();
-        return new UsesReflectionVisitor(
-            annotationParsingContext(descriptor), parent::accept, this::setContext, classItem);
+        return new UsesReflectionVisitor(parsingContext, parent::accept, setContext, classItem);
       }
-      if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
-        return new ForApiClassVisitor(
-            annotationParsingContext(descriptor), parent::accept, this::setContext, className);
+      if (descriptor.equals(ForApi.DESCRIPTOR)) {
+        return new ForApiClassVisitor(parsingContext, parent::accept, setContext, className);
       }
-      if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
+      if (descriptor.equals(UsedByReflection.DESCRIPTOR)
           || descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
         return new UsedByReflectionClassVisitor(
-            annotationParsingContext(descriptor),
-            parent::accept,
-            this::setContext,
-            className);
+            parsingContext, parent::accept, setContext, className);
       }
       if (descriptor.equals(AnnotationConstants.CheckRemoved.DESCRIPTOR)) {
         return new CheckRemovedClassVisitor(
-            annotationParsingContext(descriptor),
-            parent::accept,
-            this::setContext,
-            className,
-            KeepCheckKind.REMOVED);
+            parsingContext, parent::accept, setContext, className, KeepCheckKind.REMOVED);
       }
       if (descriptor.equals(AnnotationConstants.CheckOptimizedOut.DESCRIPTOR)) {
         return new CheckRemovedClassVisitor(
-            annotationParsingContext(descriptor),
-            parent::accept,
-            this::setContext,
-            className,
-            KeepCheckKind.OPTIMIZED_OUT);
+            parsingContext, parent::accept, setContext, className, KeepCheckKind.OPTIMIZED_OUT);
       }
       return null;
     }
@@ -334,7 +449,8 @@
           new MethodParsingContext(classParsingContext, methodName, methodDescriptor);
     }
 
-    private KeepMemberItemPattern createMethodItemContext() {
+    private static KeepMemberItemPattern createMethodItemContext(
+        String className, String methodName, String methodDescriptor) {
       String returnTypeDescriptor = Type.getReturnType(methodDescriptor).getDescriptor();
       Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor);
       KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder();
@@ -363,50 +479,77 @@
 
     @Override
     public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+      return createAnnotationVisitor(
+          descriptor,
+          visible,
+          true,
+          false,
+          parent::accept,
+          annotationParsingContext(descriptor),
+          className,
+          methodName,
+          methodDescriptor,
+          this::setContext);
+    }
+
+    public static AnnotationVisitor createAnnotationVisitor(
+        String descriptor,
+        boolean visible,
+        boolean readEmbedded,
+        boolean readExtracted,
+        Consumer<KeepDeclaration> parent,
+        AnnotationParsingContext parsingContext,
+        String className,
+        String methodName,
+        String methodDescriptor,
+        Consumer<KeepEdgeMetaInfo.Builder> setContext) {
       // Skip any visible annotations as @KeepEdge is not runtime visible.
       if (visible) {
         return null;
       }
+      if (!readEmbedded) {
+        // Only the embedded annotations can be on fields.
+        return null;
+      }
       if (descriptor.equals(Edge.DESCRIPTOR)) {
-        return new KeepEdgeVisitor(
-            annotationParsingContext(descriptor), parent::accept, this::setContext);
+        return new KeepEdgeVisitor(parsingContext, parent::accept, setContext);
       }
       if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
         return new UsesReflectionVisitor(
-            annotationParsingContext(descriptor),
+            parsingContext,
             parent::accept,
-            this::setContext,
-            createMethodItemContext());
+            setContext,
+            createMethodItemContext(className, methodName, methodDescriptor));
       }
       if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
         return new ForApiMemberVisitor(
-            annotationParsingContext(descriptor),
+            parsingContext,
             parent::accept,
-            this::setContext,
-            createMethodItemContext());
+            setContext,
+            createMethodItemContext(className, methodName, methodDescriptor));
       }
       if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
           || descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
         return new UsedByReflectionMemberVisitor(
-            annotationParsingContext(descriptor),
+            parsingContext,
             parent::accept,
-            this::setContext,
-            createMethodItemContext());
+            setContext,
+            createMethodItemContext(className, methodName, methodDescriptor));
       }
       if (descriptor.equals(AnnotationConstants.CheckRemoved.DESCRIPTOR)) {
         return new CheckRemovedMemberVisitor(
-            annotationParsingContext(descriptor),
+            parsingContext,
             parent::accept,
-            this::setContext,
-            createMethodItemContext(),
+            setContext,
+            createMethodItemContext(className, methodName, methodDescriptor),
             KeepCheckKind.REMOVED);
       }
       if (descriptor.equals(AnnotationConstants.CheckOptimizedOut.DESCRIPTOR)) {
         return new CheckRemovedMemberVisitor(
-            annotationParsingContext(descriptor),
+            parsingContext,
             parent::accept,
-            this::setContext,
-            createMethodItemContext(),
+            setContext,
+            createMethodItemContext(className, methodName, methodDescriptor),
             KeepCheckKind.OPTIMIZED_OUT);
       }
       return null;
@@ -422,7 +565,7 @@
     private final Parent<KeepEdge> parent;
     private final String className;
     private final String fieldName;
-    private final String fieldDescriptor;
+    private final String fieldTypeDescriptor;
     private final FieldParsingContext parsingContext;
 
     KeepEdgeFieldVisitor(
@@ -430,23 +573,24 @@
         Parent<KeepEdge> parent,
         String className,
         String fieldName,
-        String fieldDescriptor) {
+        String fieldTypeDescriptor) {
       super(ASM_VERSION);
       this.parent = parent;
       this.className = className;
       this.fieldName = fieldName;
-      this.fieldDescriptor = fieldDescriptor;
+      this.fieldTypeDescriptor = fieldTypeDescriptor;
       this.parsingContext =
-          new FieldParsingContext(classParsingContext, fieldName, fieldDescriptor);
+          new FieldParsingContext(classParsingContext, fieldName, fieldTypeDescriptor);
     }
 
     private AnnotationParsingContext annotationParsingContext(String descriptor) {
       return parsingContext.annotation(descriptor);
     }
 
-    private KeepMemberItemPattern createMemberItemContext() {
+    private static KeepMemberItemPattern createMemberItemContext(
+        String className, String fieldName, String fieldTypeDescriptor) {
       KeepFieldTypePattern typePattern =
-          KeepFieldTypePattern.fromType(KeepTypePattern.fromDescriptor(fieldDescriptor));
+          KeepFieldTypePattern.fromType(KeepTypePattern.fromDescriptor(fieldTypeDescriptor));
       return KeepMemberItemPattern.builder()
           .setClassReference(classReferenceFromName(className))
           .setMemberPattern(
@@ -459,39 +603,67 @@
 
     private void setContext(KeepEdgeMetaInfo.Builder builder) {
       builder.setContextFromFieldDescriptor(
-          KeepEdgeReaderUtils.getDescriptorFromJavaType(className), fieldName, fieldDescriptor);
+          KeepEdgeReaderUtils.getDescriptorFromJavaType(className), fieldName, fieldTypeDescriptor);
     }
 
     @Override
     public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+      return createAnnotationVisitor(
+          descriptor,
+          visible,
+          true,
+          false,
+          parent::accept,
+          annotationParsingContext(descriptor),
+          className,
+          fieldName,
+          fieldTypeDescriptor,
+          this::setContext);
+    }
+
+    public static AnnotationVisitorBase createAnnotationVisitor(
+        String descriptor,
+        boolean visible,
+        boolean readEmbedded,
+        boolean readExtracted,
+        Consumer<KeepEdge> parent,
+        AnnotationParsingContext parsingContext,
+        String className,
+        String fieldName,
+        String fieldTypeDescriptor,
+        Consumer<KeepEdgeMetaInfo.Builder> setContext) {
       // Skip any visible annotations as @KeepEdge is not runtime visible.
       if (visible) {
         return null;
       }
+      if (!readEmbedded) {
+        // Only the embedded annotations can be on fields.
+        return null;
+      }
       if (descriptor.equals(Edge.DESCRIPTOR)) {
-        return new KeepEdgeVisitor(annotationParsingContext(descriptor), parent, this::setContext);
+        return new KeepEdgeVisitor(parsingContext, parent::accept, setContext);
       }
       if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
         return new UsesReflectionVisitor(
-            annotationParsingContext(descriptor),
-            parent,
-            this::setContext,
-            createMemberItemContext());
+            parsingContext,
+            parent::accept,
+            setContext,
+            createMemberItemContext(className, fieldName, fieldTypeDescriptor));
       }
-      if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
+      if (descriptor.equals(ForApi.DESCRIPTOR)) {
         return new ForApiMemberVisitor(
-            annotationParsingContext(descriptor),
-            parent,
-            this::setContext,
-            createMemberItemContext());
+            parsingContext,
+            parent::accept,
+            setContext,
+            createMemberItemContext(className, fieldName, fieldTypeDescriptor));
       }
-      if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
+      if (descriptor.equals(UsedByReflection.DESCRIPTOR)
           || descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
         return new UsedByReflectionMemberVisitor(
-            annotationParsingContext(descriptor),
-            parent,
-            this::setContext,
-            createMemberItemContext());
+            parsingContext,
+            parent::accept,
+            setContext,
+            createMemberItemContext(className, fieldName, fieldTypeDescriptor));
       }
       return null;
     }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
index 21645d5..78eddd6 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
@@ -20,7 +20,11 @@
   }
 
   public static String getDescriptorFromClassTypeName(String classTypeName) {
-    return "L" + getBinaryNameFromClassTypeName(classTypeName) + ";";
+    return getDescriptorFromBinaryName(getBinaryNameFromClassTypeName(classTypeName));
+  }
+
+  public static String getDescriptorFromBinaryName(String binaryName) {
+    return "L" + binaryName + ";";
   }
 
   public static String getJavaTypeFromDescriptor(String descriptor) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
index 5f6bb55..11eefa5 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.keepanno.asm.KeepEdgeReaderUtils.getJavaTypeFromDescriptor;
 
+import com.android.tools.r8.keepanno.asm.KeepEdgeReaderUtils;
 import org.objectweb.asm.Type;
 
 public abstract class ParsingContext {
@@ -62,10 +63,19 @@
   public static class ClassParsingContext extends ParsingContext {
     private final String className;
 
-    public ClassParsingContext(String className) {
+    private ClassParsingContext(String className) {
       this.className = className;
     }
 
+    public static ClassParsingContext fromName(String className) {
+      return new ClassParsingContext(className);
+    }
+
+    public static ClassParsingContext fromDescriptor(String descriptor) {
+      return ClassParsingContext.fromName(
+          KeepEdgeReaderUtils.getJavaTypeFromDescriptor(descriptor));
+    }
+
     @Override
     public String getHolderName() {
       return className;
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 978932d..4941b96 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -35,9 +35,11 @@
 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.asm.KeepEdgeReader;
+import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
 import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.FieldParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.MethodParsingContext;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticMarker;
 import com.android.tools.r8.utils.AsmUtils;
@@ -453,17 +455,25 @@
       return new CreateMethodVisitor(access, name, desc, signature, exceptions, this);
     }
 
+    public boolean shouldSkipKeepAnnotations() {
+      return classKind != ClassKind.PROGRAM
+          || !application.options.testing.isKeepAnnotationsEnabled();
+    }
+
     @Override
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
-      if (!visible && ExtractedAnnotations.DESCRIPTOR.equals(desc)) {
-        if (!application.options.testing.enableExtractedKeepAnnotations) {
+      if (KeepEdgeReader.isClassKeepAnnotation(desc, visible)) {
+        if (shouldSkipKeepAnnotations()) {
           return null;
         }
-        if (classKind != ClassKind.PROGRAM) {
-          return null;
-        }
-        return new ExtractedAnnotationsVisitor(
-            new ClassParsingContext(type.getName()).annotation(desc),
+        String className = type.getTypeName();
+        return KeepEdgeReader.createClassKeepAnnotationVisitor(
+            desc,
+            visible,
+            application.options.testing.enableEmbeddedKeepAnnotations,
+            application.options.testing.enableExtractedKeepAnnotations,
+            className,
+            ClassParsingContext.fromName(className).annotation(desc),
             application::addKeepDeclaration);
       }
       return createAnnotationVisitor(
@@ -660,7 +670,7 @@
     private final CreateDexClassVisitor<?> parent;
     private final int access;
     private final String name;
-    private final String desc;
+    private final String fieldTypeDescriptor;
     private final Object value;
     private final FieldTypeSignature fieldSignature;
     private List<DexAnnotation> annotations = null;
@@ -676,7 +686,7 @@
       this.parent = parent;
       this.access = access;
       this.name = name;
-      this.desc = desc;
+      this.fieldTypeDescriptor = desc;
       this.value = value;
       this.fieldSignature =
           parent.application.options.parseSignatureAttribute()
@@ -691,6 +701,26 @@
 
     @Override
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      if (KeepEdgeReader.isFieldKeepAnnotation(desc, visible)) {
+        if (parent.shouldSkipKeepAnnotations()) {
+          return null;
+        }
+        String className = parent.type.getTypeName();
+        AnnotationParsingContext parsingContext =
+            new FieldParsingContext(
+                    ClassParsingContext.fromName(className), name, fieldTypeDescriptor)
+                .annotation(desc);
+        return KeepEdgeReader.createFieldKeepAnnotationVisitor(
+            desc,
+            visible,
+            parent.application.options.testing.enableEmbeddedKeepAnnotations,
+            parent.application.options.testing.enableExtractedKeepAnnotations,
+            className,
+            name,
+            fieldTypeDescriptor,
+            parsingContext,
+            parent.application::addKeepDeclaration);
+      }
       return createAnnotationVisitor(
           desc, visible, getAnnotations(), parent.application, DexAnnotation::new);
     }
@@ -705,7 +735,7 @@
     @Override
     public void visitEnd() {
       FieldAccessFlags flags = createFieldAccessFlags(access);
-      DexField dexField = parent.application.getField(parent.type, name, desc);
+      DexField dexField = parent.application.getField(parent.type, name, fieldTypeDescriptor);
       parent.application.checkFieldForRecord(dexField, parent.classKind);
       parent.application.checkFieldForMethodHandlesLookup(dexField, parent.classKind);
       parent.application.checkFieldForVarHandle(dexField, parent.classKind);
@@ -785,6 +815,7 @@
   private static class CreateMethodVisitor extends MethodVisitor {
 
     private final String name;
+    private final String methodDescriptor;
     final CreateDexClassVisitor<?> parent;
     private final int parameterCount;
     private List<DexAnnotation> annotations = null;
@@ -808,6 +839,7 @@
         CreateDexClassVisitor<?> parent) {
       super(ASM_VERSION);
       this.name = name;
+      this.methodDescriptor = desc;
       this.parent = parent;
       this.method = parent.application.getMethod(parent.type, name, desc);
       this.flags = createMethodAccessFlags(name, access);
@@ -834,6 +866,26 @@
 
     @Override
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      if (KeepEdgeReader.isMethodKeepAnnotation(desc, visible)) {
+        if (parent.shouldSkipKeepAnnotations()) {
+          return null;
+        }
+        String className = parent.type.getTypeName();
+        AnnotationParsingContext parsingContext =
+            new MethodParsingContext(
+                    ClassParsingContext.fromName(className), name, methodDescriptor)
+                .annotation(desc);
+        return KeepEdgeReader.createMethodKeepAnnotationVisitor(
+            desc,
+            visible,
+            parent.application.options.testing.enableEmbeddedKeepAnnotations,
+            parent.application.options.testing.enableExtractedKeepAnnotations,
+            className,
+            name,
+            methodDescriptor,
+            parsingContext,
+            parent.application::addKeepDeclaration);
+      }
       return createAnnotationVisitor(
           desc, visible, getAnnotations(), parent.application, DexAnnotation::new);
     }
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 760cede..fdc9cbd 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1637,7 +1637,7 @@
   }
 
   void traceMethodPosition(com.android.tools.r8.ir.code.Position position, ProgramMethod context) {
-    if (!options.testing.enableExtractedKeepAnnotations) {
+    if (!options.testing.isKeepAnnotationsEnabled()) {
       // Currently inlining is only intended for the evaluation of keep annotation edges.
       return;
     }
@@ -3452,6 +3452,7 @@
   }
 
   public boolean isOriginalReferenceEffectivelyLive(DexReference reference) {
+    assert options.testing.isKeepAnnotationsEnabled();
     // The effectively-live original set contains types, fields and methods witnessed by
     // instructions, such as method inlining positions.
     return effectivelyLiveOriginalReferences.contains(reference);
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 8e9e3e1..f284b2c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2184,6 +2184,11 @@
   public static class TestingOptions {
 
     public boolean enableExtractedKeepAnnotations = false;
+    public boolean enableEmbeddedKeepAnnotations = false;
+
+    public boolean isKeepAnnotationsEnabled() {
+      return enableExtractedKeepAnnotations || enableEmbeddedKeepAnnotations;
+    }
 
     public boolean enableNumberUnboxer = false;
     public boolean printNumberUnboxed = false;
diff --git a/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java
index 59915bc9..656bb6b 100644
--- a/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
@@ -54,12 +55,12 @@
   }
 
   @Test
-  public void testR8Native() throws Throwable {
-    assumeTrue(parameters.isR8() && parameters.isNative());
+  public void testCurrentR8() throws Throwable {
+    assumeTrue(parameters.isR8() && parameters.isCurrentR8());
     testForKeepAnno(parameters)
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
-        .applyIfR8Native(
+        .applyIfR8Current(
             b ->
                 b.allowDiagnosticWarningMessages()
                     .setDiagnosticsLevelModifier(
@@ -86,8 +87,9 @@
   }
 
   @Test
-  public void testR8Extract() throws Throwable {
-    assumeTrue(parameters.isR8() && !parameters.isNative());
+  public void testLegacyR8() throws Throwable {
+    assumeTrue(parameters.isR8() && !parameters.isCurrentR8());
+    assertTrue(parameters.isLegacyR8());
     try {
       testForKeepAnno(parameters)
           .addProgramClasses(getInputClasses())
diff --git a/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java
index b3bedf1..4010ef8 100644
--- a/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
@@ -53,12 +54,12 @@
   }
 
   @Test
-  public void testR8Native() throws Exception {
-    assumeTrue(parameters.isR8() && parameters.isNative());
+  public void testCurrentR8() throws Exception {
+    assumeTrue(parameters.isR8() && parameters.isCurrentR8());
     testForKeepAnno(parameters)
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
-        .applyIfR8Native(
+        .applyIfR8Current(
             b ->
                 b.allowDiagnosticWarningMessages()
                     .setDiagnosticsLevelModifier(
@@ -89,8 +90,9 @@
   }
 
   @Test
-  public void testR8Extract() throws Throwable {
-    assumeTrue(parameters.isR8() && !parameters.isNative());
+  public void testLegacyR8() throws Throwable {
+    assumeTrue(parameters.isR8() && !parameters.isCurrentR8());
+    assertTrue(parameters.isLegacyR8());
     try {
       testForKeepAnno(parameters)
           .addProgramClasses(getInputClasses())
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoParameters.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoParameters.java
index 53c7634..04e7bf8 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoParameters.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoParameters.java
@@ -13,7 +13,8 @@
   public enum KeepAnnoConfig {
     REFERENCE,
     R8_DIRECT,
-    R8_EXTRACT,
+    R8_NORMALIZED,
+    R8_RULES,
     R8_LEGACY,
     PG;
   }
@@ -56,21 +57,27 @@
   }
 
   public boolean isR8() {
-    return config == KeepAnnoConfig.R8_DIRECT
-        || config == KeepAnnoConfig.R8_EXTRACT
-        || config == KeepAnnoConfig.R8_LEGACY;
+    return isCurrentR8() || isLegacyR8();
   }
 
   public boolean isPG() {
     return config == KeepAnnoConfig.PG;
   }
 
-  public boolean isNative() {
-    return config == KeepAnnoConfig.R8_DIRECT || config == KeepAnnoConfig.R8_EXTRACT;
+  public boolean isCurrentR8() {
+    return isNativeR8() || config == KeepAnnoConfig.R8_RULES;
   }
 
-  public boolean isExtract() {
-    return config == KeepAnnoConfig.R8_EXTRACT
+  public boolean isLegacyR8() {
+    return config == KeepAnnoConfig.R8_LEGACY;
+  }
+
+  public boolean isNativeR8() {
+    return config == KeepAnnoConfig.R8_DIRECT || config == KeepAnnoConfig.R8_NORMALIZED;
+  }
+
+  public boolean isExtractRules() {
+    return config == KeepAnnoConfig.R8_RULES
         || config == KeepAnnoConfig.R8_LEGACY
         || config == KeepAnnoConfig.PG;
   }
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBase.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBase.java
index 86d9529..35c00ff 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBase.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBase.java
@@ -22,7 +22,9 @@
       keepAnnoParams.add(
           new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_DIRECT));
       keepAnnoParams.add(
-          new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_EXTRACT));
+          new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_NORMALIZED));
+      keepAnnoParams.add(
+          new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_RULES));
       keepAnnoParams.add(
           new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_LEGACY));
       if (parameters.isCfRuntime()) {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
index 9a3c1e8..1105bd3 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.keepanno.KeepAnnoParameters.KeepAnnoConfig;
 import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
 import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
 import com.android.tools.r8.keepanno.ast.KeepDeclaration;
@@ -41,9 +42,9 @@
       case REFERENCE:
         return new ReferenceBuilder(params, temp);
       case R8_DIRECT:
-        return new R8NativeBuilder(false, params, temp);
-      case R8_EXTRACT:
-        return new R8NativeBuilder(true, params, temp);
+      case R8_NORMALIZED:
+      case R8_RULES:
+        return new R8NativeBuilder(params, temp);
       case R8_LEGACY:
         return new R8LegacyBuilder(params, temp);
       case PG:
@@ -96,7 +97,7 @@
     return this;
   }
 
-  public KeepAnnoTestBuilder applyIfR8Native(ThrowableConsumer<R8TestBuilder<?>> builderConsumer) {
+  public KeepAnnoTestBuilder applyIfR8Current(ThrowableConsumer<R8TestBuilder<?>> builderConsumer) {
     return this;
   }
 
@@ -113,7 +114,7 @@
   }
 
   public final KeepAnnoTestBuilder allowUnusedProguardConfigurationRules() {
-    return applyIfR8Native(R8TestBuilder::allowUnusedProguardConfigurationRules);
+    return applyIfR8Current(R8TestBuilder::allowUnusedProguardConfigurationRules);
   }
 
   public final KeepAnnoTestBuilder allowAccessModification() {
@@ -167,17 +168,18 @@
 
     private final R8FullTestBuilder builder;
     private List<Consumer<R8TestCompileResult>> compileResultConsumers = new ArrayList<>();
-    private final boolean useEdgeExtraction;
+    private final boolean normalizeEdges;
+    private final boolean extractRules;
 
-    private R8NativeBuilder(
-        boolean useEdgeExtraction, KeepAnnoParameters params, TemporaryFolder temp) {
+    private R8NativeBuilder(KeepAnnoParameters params, TemporaryFolder temp) {
       super(params, temp);
       builder =
           TestBase.testForR8(temp, parameters().getBackend())
               .enableExperimentalKeepAnnotations()
               .setMinApi(parameters());
-      this.useEdgeExtraction = useEdgeExtraction;
-      if (useEdgeExtraction) {
+      extractRules = params.config() == KeepAnnoConfig.R8_RULES;
+      normalizeEdges = params.config() == KeepAnnoConfig.R8_NORMALIZED;
+      if (normalizeEdges) {
         builder.getBuilder().setEnableExperimentalKeepAnnotations(false);
         builder.getBuilder().setEnableExperimentalExtractedKeepAnnotations(true);
       } else {
@@ -188,12 +190,18 @@
 
     @Override
     public KeepAnnoTestBuilder enableNativeInterpretation() {
-      if (useEdgeExtraction) {
-        // This enables native interpretation of the extracted edges.
-        builder.addOptionsModification(o -> o.testing.enableExtractedKeepAnnotations = true);
-        // This disables converting the extracted edges to PG rules in the command reader.
-        builder.getBuilder().setEnableExperimentalExtractedKeepAnnotations(false);
+      if (extractRules) {
+        return this;
       }
+      // This enables native interpretation of all keep annotations.
+      builder.addOptionsModification(
+          o -> {
+            o.testing.enableExtractedKeepAnnotations = true;
+            o.testing.enableEmbeddedKeepAnnotations = true;
+          });
+      // This disables all reading of annotations in the command reader.
+      builder.getBuilder().setEnableExperimentalKeepAnnotations(false);
+      builder.getBuilder().setEnableExperimentalExtractedKeepAnnotations(false);
       return this;
     }
 
@@ -205,7 +213,7 @@
     }
 
     @Override
-    public KeepAnnoTestBuilder applyIfR8Native(
+    public KeepAnnoTestBuilder applyIfR8Current(
         ThrowableConsumer<R8TestBuilder<?>> builderConsumer) {
       builderConsumer.acceptWithRuntimeException(builder);
       return this;
@@ -237,7 +245,7 @@
 
     private void extractAndAdd(byte[] classFileData) {
       builder.addProgramClassFileData(classFileData);
-      if (useEdgeExtraction) {
+      if (normalizeEdges) {
         List<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(classFileData);
         if (!declarations.isEmpty()) {
           String binaryName =
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java
index 44306ed..a80be3c 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
 import com.android.tools.r8.keepanno.annotations.KeepTarget;
@@ -13,6 +16,7 @@
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -53,6 +57,18 @@
 
   private void checkOutput(CodeInspector inspector) {
     assertThat(inspector.clazz(B.class), isPresentAndNotRenamed());
+    ClassSubject classA = inspector.clazz(A.class);
+    if (parameters.isNativeR8()) {
+      assertThat(classA, isAbsent());
+    } else {
+      assertTrue(parameters.isExtractRules());
+      // PG and R8 with keep rules will keep the residual class.
+      assertThat(classA, isPresentAndRenamed());
+      // R8 using keep rules will soft-pin the precondition method too.
+      assertThat(
+          classA.uniqueMethodWithOriginalName("foo"),
+          parameters.isPG() ? isAbsent() : isPresentAndRenamed());
+    }
   }
 
   static class A {
@@ -60,7 +76,7 @@
     // Pattern includes any members
     @UsesReflection(@KeepTarget(classConstant = B.class, kind = KeepItemKind.CLASS_AND_MEMBERS))
     public void foo() throws Exception {
-      String typeName = B.class.getTypeName();
+      String typeName = B.class.getName();
       int memberCount = B.class.getDeclaredMethods().length + B.class.getDeclaredFields().length;
       System.out.println(typeName.substring(typeName.length() - 1) + ", #members: " + memberCount);
     }