Merge commit 'deb99884c39a3814749bc59c4d10eaaac3dffbd4' into dev-release

Change-Id: I76528df9229f5657730a8affd903a4fd00517e70
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index effc693..fe9bd92 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -356,7 +356,7 @@
   const val javassist = "3.29.2-GA"
   const val junitVersion = "4.13-beta-2"
   const val kotlinVersion = "1.9.0"
-  const val kotlinMetadataVersion = "0.9.0"
+  const val kotlinMetadataVersion = "0.7.0"
   const val mockito = "2.10.0"
   const val smaliVersion = "3.0.3"
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
index a175b3d..a2f7bb9 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
@@ -23,11 +23,48 @@
 public @interface ClassNamePattern {
 
   /**
+   * Define the class-name pattern by fully qualified class name.
+   *
+   * <p>Mutually exclusive with the following other properties defining class-name:
+   *
+   * <ul>
+   *   <li>constant
+   *   <li>simpleName
+   *   <li>packageName
+   * </ul>
+   *
+   * @return The qualified class name that defines the class.
+   */
+  String name() default "";
+
+  /**
+   * Define the class-name pattern by reference to a Class constant.
+   *
+   * <p>Mutually exclusive with the following other properties defining class-name:
+   *
+   * <ul>
+   *   <li>name
+   *   <li>simpleName
+   *   <li>packageName
+   * </ul>
+   *
+   * @return The class-constant that defines the class.
+   */
+  Class<?> constant() default Object.class;
+
+  /**
    * Exact simple name of the class or interface.
    *
    * <p>For example, the simple name of {@code com.example.MyClass} is {@code MyClass}.
    *
    * <p>The default matches any simple name.
+   *
+   * <p>Mutually exclusive with the following other properties defining class-simple-name:
+   *
+   * <ul>
+   *   <li>name
+   *   <li>constant
+   * </ul>
    */
   String simpleName() default "";
 
@@ -37,6 +74,13 @@
    * <p>For example, the package of {@code com.example.MyClass} is {@code com.example}.
    *
    * <p>The default matches any package.
+   *
+   * <p>Mutually exclusive with the following other properties defining class-package-name:
+   *
+   * <ul>
+   *   <li>name
+   *   <li>constant
+   * </ul>
    */
   String packageName() default "";
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation.java
new file mode 100644
index 0000000..e2e9461
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.annotations;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.CLASS)
+@Repeatable(ExtractedKeepAnnotations.class)
+public @interface ExtractedKeepAnnotation {
+  /**
+   * The version defining this extracted keep annotation.
+   *
+   * <p>Note: this version property must be the first property defined. Its content may determine
+   * the subsequent parsing.
+   */
+  String version();
+
+  /**
+   * The context giving rise to this extracted keep annotation.
+   *
+   * <p>The context must be a class descriptor, method descriptor or field descriptor.
+   */
+  String context();
+
+  /** The extracted edge. */
+  KeepEdge edge();
+
+  boolean isCheckRemoved() default false;
+
+  boolean isCheckOptimizedOut() default false;
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations.java
index 43a91bb..f6855cc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations.java
@@ -8,24 +8,15 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * Collection of extracted keep annotations.
+ *
+ * <p>This annotation is just a collection of the extracted annotations. It is version independent
+ * and is assumed to never change. Any version specific changes are to be made within the single
+ * element structure of {@link ExtractedKeepAnnotation}.
+ */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.CLASS)
 public @interface ExtractedKeepAnnotations {
-  /**
-   * The version of defining this extracted keep annotation.
-   *
-   * <p>Note: this version property must be the first property defined. Its content may determine
-   * the subsequent parsing.
-   */
-  String version();
-
-  /**
-   * The context giving rise to this extracted keep annotation.
-   *
-   * <p>The context must be a class descriptor, method descriptor or field descriptor.
-   */
-  String context();
-
-  /** The extracted edges. */
-  KeepEdge[] edges();
+  ExtractedKeepAnnotation[] value();
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java
new file mode 100644
index 0000000..591d750
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
+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 pattern structure for matching instances of classes and interfaces.
+ *
+ * <p>If no properties are set, the default pattern matches any instance.
+ */
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface InstanceOfPattern {
+
+  /**
+   * True if the pattern should include the directly matched classes.
+   *
+   * <p>If false, the pattern is exclusive and only matches classes that are strict subclasses of
+   * the pattern.
+   */
+  boolean inclusive() default true;
+
+  /** Instances of classes matching the class-name pattern. */
+  ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+}
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 364f3a1..f92ab49 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
@@ -79,6 +79,7 @@
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstant
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classAnnotatedByClassName
    *   <li>classAnnotatedByClassConstant
    *   <li>classAnnotatedByClassNamePattern
@@ -150,6 +151,7 @@
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstant
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -171,6 +173,7 @@
    *   <li>instanceOfClassName
    *   <li>instanceOfClassConstant
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -189,6 +192,7 @@
    *   <li>instanceOfClassName
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -210,6 +214,7 @@
    *   <li>instanceOfClassName
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstant
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -220,6 +225,25 @@
   Class<?> instanceOfClassConstantExclusive() default Object.class;
 
   /**
+   * Define the instance-of with a pattern.
+   *
+   * <p>Mutually exclusive with the following other properties defining instance-of:
+   *
+   * <ul>
+   *   <li>instanceOfClassName
+   *   <li>instanceOfClassNameExclusive
+   *   <li>instanceOfClassConstant
+   *   <li>instanceOfClassConstantExclusive
+   *   <li>classFromBinding
+   * </ul>
+   *
+   * <p>If none are specified the default is to match any class instance.
+   *
+   * @return The pattern that defines what instance-of the class must be.
+   */
+  InstanceOfPattern instanceOfPattern() default @InstanceOfPattern();
+
+  /**
    * Define the class-annotated-by pattern by fully qualified class name.
    *
    * <p>Mutually exclusive with the following other properties defining class-annotated-by:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index 3a96660..65d13d8 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -41,6 +41,7 @@
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstant
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classAnnotatedByClassName
    *   <li>classAnnotatedByClassConstant
    *   <li>classAnnotatedByClassNamePattern
@@ -112,6 +113,7 @@
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstant
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -133,6 +135,7 @@
    *   <li>instanceOfClassName
    *   <li>instanceOfClassConstant
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -151,6 +154,7 @@
    *   <li>instanceOfClassName
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -172,6 +176,7 @@
    *   <li>instanceOfClassName
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstant
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -182,6 +187,25 @@
   Class<?> instanceOfClassConstantExclusive() default Object.class;
 
   /**
+   * Define the instance-of with a pattern.
+   *
+   * <p>Mutually exclusive with the following other properties defining instance-of:
+   *
+   * <ul>
+   *   <li>instanceOfClassName
+   *   <li>instanceOfClassNameExclusive
+   *   <li>instanceOfClassConstant
+   *   <li>instanceOfClassConstantExclusive
+   *   <li>classFromBinding
+   * </ul>
+   *
+   * <p>If none are specified the default is to match any class instance.
+   *
+   * @return The pattern that defines what instance-of the class must be.
+   */
+  InstanceOfPattern instanceOfPattern() default @InstanceOfPattern();
+
+  /**
    * Define the class-annotated-by pattern by fully qualified class name.
    *
    * <p>Mutually exclusive with the following other properties defining class-annotated-by:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index 20e5492..e7e8c74 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -138,6 +138,7 @@
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstant
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classAnnotatedByClassName
    *   <li>classAnnotatedByClassConstant
    *   <li>classAnnotatedByClassNamePattern
@@ -209,6 +210,7 @@
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstant
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -230,6 +232,7 @@
    *   <li>instanceOfClassName
    *   <li>instanceOfClassConstant
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -248,6 +251,7 @@
    *   <li>instanceOfClassName
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstantExclusive
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -269,6 +273,7 @@
    *   <li>instanceOfClassName
    *   <li>instanceOfClassNameExclusive
    *   <li>instanceOfClassConstant
+   *   <li>instanceOfPattern
    *   <li>classFromBinding
    * </ul>
    *
@@ -279,6 +284,25 @@
   Class<?> instanceOfClassConstantExclusive() default Object.class;
 
   /**
+   * Define the instance-of with a pattern.
+   *
+   * <p>Mutually exclusive with the following other properties defining instance-of:
+   *
+   * <ul>
+   *   <li>instanceOfClassName
+   *   <li>instanceOfClassNameExclusive
+   *   <li>instanceOfClassConstant
+   *   <li>instanceOfClassConstantExclusive
+   *   <li>classFromBinding
+   * </ul>
+   *
+   * <p>If none are specified the default is to match any class instance.
+   *
+   * @return The pattern that defines what instance-of the class must be.
+   */
+  InstanceOfPattern instanceOfPattern() default @InstanceOfPattern();
+
+  /**
    * Define the class-annotated-by pattern by fully qualified class name.
    *
    * <p>Mutually exclusive with the following other properties defining class-annotated-by:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/BooleanParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/BooleanParser.java
new file mode 100644
index 0000000..54202fb
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/BooleanParser.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.keepanno.asm;
+
+import com.android.tools.r8.keepanno.asm.BooleanParser.BooleanProperty;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import java.util.function.Consumer;
+
+public class BooleanParser extends PropertyParserBase<Boolean, BooleanProperty> {
+
+  public BooleanParser(ParsingContext parsingContext) {
+    super(parsingContext);
+  }
+
+  public enum BooleanProperty {
+    BOOL
+  }
+
+  @Override
+  public boolean tryProperty(
+      BooleanProperty property, String name, Object value, Consumer<Boolean> setValue) {
+    if (BooleanProperty.BOOL.equals(property) && value instanceof Boolean) {
+      setValue.accept((Boolean) value);
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
index 6bc17aa..b3fdddb 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -83,21 +83,35 @@
         {
           AnnotationParsingContext parsingContext =
               getParsingContext().property(name).annotation(descriptor);
+          ClassNameParser fullNameParser = new ClassNameParser(parsingContext);
           PackageNameParser packageParser = new PackageNameParser(parsingContext);
-          ClassSimpleNameParser nameParser = new ClassSimpleNameParser(parsingContext);
+          ClassSimpleNameParser simpleNameParser = new ClassSimpleNameParser(parsingContext);
+          fullNameParser.setProperty(ClassNamePattern.name, ClassNameProperty.NAME);
+          fullNameParser.setProperty(ClassNamePattern.constant, ClassNameProperty.CONSTANT);
           packageParser.setProperty(ClassNamePattern.packageName, PackageNameProperty.NAME);
-          nameParser.setProperty(ClassNamePattern.simpleName, ClassSimpleNameProperty.NAME);
+          simpleNameParser.setProperty(ClassNamePattern.simpleName, ClassSimpleNameProperty.NAME);
           return new ParserVisitor(
               parsingContext,
-              ImmutableList.of(packageParser, nameParser),
-              () ->
-                  setValue.accept(
-                      KeepQualifiedClassNamePattern.builder()
-                          .setPackagePattern(
-                              packageParser.getValueOrDefault(KeepPackagePattern.any()))
-                          .setNamePattern(
-                              nameParser.getValueOrDefault(KeepUnqualfiedClassNamePattern.any()))
-                          .build()));
+              ImmutableList.of(fullNameParser, packageParser, simpleNameParser),
+              () -> {
+                if (fullNameParser.isDeclared()) {
+                  if (simpleNameParser.isDeclared() || packageParser.isDeclared()) {
+                    throw parsingContext.error(
+                        "Cannot specify both the full class name and its "
+                            + (simpleNameParser.isDeclared() ? "simple name" : "package"));
+                  }
+                  setValue.accept(fullNameParser.getValue());
+                  return;
+                }
+                setValue.accept(
+                    KeepQualifiedClassNamePattern.builder()
+                        .setPackagePattern(
+                            packageParser.getValueOrDefault(KeepPackagePattern.any()))
+                        .setNamePattern(
+                            simpleNameParser.getValueOrDefault(
+                                KeepUnqualfiedClassNamePattern.any()))
+                        .build());
+              });
         }
       default:
         return null;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ContextDescriptor.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ContextDescriptor.java
new file mode 100644
index 0000000..66bb6d5
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ContextDescriptor.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.keepanno.asm;
+
+import static com.android.tools.r8.keepanno.ast.KeepTypePattern.fromDescriptor;
+
+import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import org.objectweb.asm.Type;
+
+public class ContextDescriptor {
+
+  public static ContextDescriptor parse(String descriptor, ParsingContext parsingContext) {
+    int classDescriptorEnd = descriptor.indexOf(';') + 1;
+    if (classDescriptorEnd <= 0) {
+      throw parsingContext.error("Invalid descriptor: " + descriptor);
+    }
+    String classDescriptor = descriptor.substring(0, classDescriptorEnd);
+    if (classDescriptorEnd == descriptor.length()) {
+      return new ContextDescriptor(classDescriptor);
+    }
+    int memberNameEnd = descriptor.indexOf('(', classDescriptorEnd);
+    if (memberNameEnd < 0) {
+      memberNameEnd = descriptor.indexOf(':', classDescriptorEnd);
+    }
+    if (memberNameEnd < 0) {
+      throw parsingContext.error("Invalid descriptor: " + descriptor);
+    }
+    String memberName = descriptor.substring(classDescriptorEnd, memberNameEnd);
+    String memberDescriptor = descriptor.substring(memberNameEnd);
+    return new ContextDescriptor(classDescriptor, memberName, memberDescriptor);
+  }
+
+  private final String classDescriptor;
+  private final String memberName;
+  private final String memberDescriptor;
+
+  private ContextDescriptor(String classDescriptor) {
+    this(classDescriptor, null, null);
+  }
+
+  private ContextDescriptor(String classDescriptor, String memberName, String memberDescriptor) {
+    this.classDescriptor = classDescriptor;
+    this.memberName = memberName;
+    this.memberDescriptor = memberDescriptor;
+  }
+
+  public boolean isClassContext() {
+    return memberDescriptor == null;
+  }
+
+  public boolean isMethodContext() {
+    return memberDescriptor != null && memberDescriptor.charAt(0) == '(';
+  }
+
+  public boolean isFieldContext() {
+    return memberDescriptor != null && memberDescriptor.charAt(0) == ':';
+  }
+
+  public String getClassDescriptor() {
+    return classDescriptor;
+  }
+
+  public String getMemberName() {
+    return memberName;
+  }
+
+  public String getMethodDescriptor() {
+    assert isMethodContext();
+    return memberDescriptor;
+  }
+
+  public String getFieldType() {
+    assert isFieldContext();
+    return memberDescriptor.substring(1);
+  }
+
+  public KeepEdgeMetaInfo.Builder applyToMetadata(KeepEdgeMetaInfo.Builder metaInfoBuilder) {
+    if (isClassContext()) {
+      metaInfoBuilder.setContextFromClassDescriptor(getClassDescriptor());
+    } else if (isMethodContext()) {
+      metaInfoBuilder.setContextFromMethodDescriptor(
+          getClassDescriptor(), getMemberName(), getMethodDescriptor());
+    } else {
+      assert isFieldContext();
+      metaInfoBuilder.setContextFromFieldDescriptor(
+          getClassDescriptor(), getMemberName(), getFieldType());
+    }
+    return metaInfoBuilder;
+  }
+
+  public KeepItemPattern toItemPattern() {
+    KeepQualifiedClassNamePattern className =
+        KeepQualifiedClassNamePattern.exactFromDescriptor(getClassDescriptor());
+    KeepClassItemPattern classItem =
+        KeepClassItemPattern.builder().setClassNamePattern(className).build();
+    if (isClassContext()) {
+      return classItem;
+    }
+    KeepMemberPattern memberPattern;
+    if (isMethodContext()) {
+      memberPattern = createMethodPatternFromDescriptor();
+    } else {
+      assert isFieldContext();
+      memberPattern = createFieldPatternFromDescriptor();
+    }
+    return KeepMemberItemPattern.builder()
+        .setClassReference(classItem.toClassItemReference())
+        .setMemberPattern(memberPattern)
+        .build();
+  }
+
+  private KeepFieldPattern createFieldPatternFromDescriptor() {
+    return KeepFieldPattern.builder()
+        .setNamePattern(KeepFieldNamePattern.exact(getMemberName()))
+        .setTypePattern(KeepFieldTypePattern.fromType(fromDescriptor(getFieldType())))
+        .build();
+  }
+
+  private KeepMethodPattern createMethodPatternFromDescriptor() {
+    Type methodType = Type.getMethodType(getMethodDescriptor());
+
+    String returnTypeDescriptor = methodType.getReturnType().getDescriptor();
+    KeepMethodReturnTypePattern returnType =
+        returnTypeDescriptor.equals("V")
+            ? KeepMethodReturnTypePattern.voidType()
+            : KeepMethodReturnTypePattern.fromType(fromDescriptor(returnTypeDescriptor));
+
+    KeepMethodParametersPattern.Builder paramBuilder = KeepMethodParametersPattern.builder();
+    for (Type argumentType : methodType.getArgumentTypes()) {
+      paramBuilder.addParameterTypePattern(fromDescriptor(argumentType.getDescriptor()));
+    }
+
+    return KeepMethodPattern.builder()
+        .setNamePattern(KeepMethodNamePattern.exact(getMemberName()))
+        .setReturnTypePattern(returnType)
+        .setParametersPattern(paramBuilder.build())
+        .build();
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/InstanceOfParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/InstanceOfParser.java
index a34915b..73b4905 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/InstanceOfParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/InstanceOfParser.java
@@ -4,12 +4,18 @@
 
 package com.android.tools.r8.keepanno.asm;
 
+import com.android.tools.r8.keepanno.asm.BooleanParser.BooleanProperty;
+import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
 import com.android.tools.r8.keepanno.asm.InstanceOfParser.InstanceOfProperties;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.InstanceOfPattern;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Item;
 import com.android.tools.r8.keepanno.ast.KeepInstanceOfPattern;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.ParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
+import com.google.common.collect.ImmutableList;
 import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.Type;
 
 public class InstanceOfParser
@@ -19,7 +25,8 @@
     NAME,
     NAME_EXCL,
     CONSTANT,
-    CONSTANT_EXCL
+    CONSTANT_EXCL,
+    PATTERN
   }
 
   public InstanceOfParser(ParsingContext parsingContext) {
@@ -64,4 +71,31 @@
         return null;
     }
   }
+
+  @Override
+  AnnotationVisitor tryPropertyAnnotation(
+      InstanceOfProperties property,
+      String name,
+      String descriptor,
+      Consumer<KeepInstanceOfPattern> setValue) {
+    if (property.equals(InstanceOfProperties.PATTERN)) {
+      AnnotationParsingContext parsingContext =
+          getParsingContext().property(name).annotation(descriptor);
+      BooleanParser inclusiveParser = new BooleanParser(parsingContext);
+      inclusiveParser.setProperty(InstanceOfPattern.inclusive, BooleanProperty.BOOL);
+      ClassNameParser classNameParser = new ClassNameParser(parsingContext);
+      classNameParser.setProperty(InstanceOfPattern.classNamePattern, ClassNameProperty.PATTERN);
+      return new ParserVisitor(
+          parsingContext,
+          ImmutableList.of(inclusiveParser, classNameParser),
+          () ->
+              setValue.accept(
+                  KeepInstanceOfPattern.builder()
+                      .setInclusive(inclusiveParser.getValueOrDefault(true))
+                      .classPattern(
+                          classNameParser.getValueOrDefault(KeepQualifiedClassNamePattern.any()))
+                      .build()));
+    }
+    return super.tryPropertyAnnotation(property, name, descriptor, setValue);
+  }
 }
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 2d5d253..01d57c7 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
@@ -13,7 +13,8 @@
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Binding;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Condition;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants.Extracted;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ExtractedAnnotation;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ExtractedAnnotations;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.FieldAccess;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.ForApi;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Item;
@@ -89,19 +90,20 @@
   public static int ASM_VERSION = ASM9;
 
   public static List<KeepDeclaration> readKeepEdges(byte[] classFileBytes) {
-    return internalReadKeepEdges(classFileBytes, false);
+    return internalReadKeepEdges(classFileBytes, true, false);
   }
 
   public static List<KeepDeclaration> readExtractedKeepEdges(byte[] classFileBytes) {
-    return internalReadKeepEdges(classFileBytes, true);
+    return internalReadKeepEdges(classFileBytes, false, true);
   }
 
   private static List<KeepDeclaration> internalReadKeepEdges(
-      byte[] classFileBytes, boolean onlyExtracted) {
+      byte[] classFileBytes, boolean readEmbedded, boolean readExtracted) {
     ClassReader reader = new ClassReader(classFileBytes);
     List<KeepDeclaration> declarations = new ArrayList<>();
     reader.accept(
-        new KeepEdgeClassVisitor(onlyExtracted, declarations::add), ClassReader.SKIP_CODE);
+        new KeepEdgeClassVisitor(readEmbedded, readExtracted, declarations::add),
+        ClassReader.SKIP_CODE);
     return declarations;
   }
 
@@ -193,14 +195,17 @@
   }
 
   private static class KeepEdgeClassVisitor extends ClassVisitor {
-    private final boolean onlyExtracted;
+    private final boolean readEmbedded;
+    private final boolean readExtracted;
     private final Parent<KeepDeclaration> parent;
     private String className;
     private ClassParsingContext parsingContext;
 
-    KeepEdgeClassVisitor(boolean onlyExtracted, Parent<KeepDeclaration> parent) {
+    KeepEdgeClassVisitor(
+        boolean readEmbedded, boolean readExtracted, Parent<KeepDeclaration> parent) {
       super(ASM_VERSION);
-      this.onlyExtracted = onlyExtracted;
+      this.readEmbedded = readEmbedded;
+      this.readExtracted = readExtracted;
       this.parent = parent;
     }
 
@@ -231,16 +236,11 @@
       if (visible) {
         return null;
       }
-      if (descriptor.equals(Extracted.DESCRIPTOR)) {
-        if (!onlyExtracted) {
-          // Annotation reading always ignores extracted edges.
-          // Note that we may reconsider this if R8 is to support a mixed-mode input.
-          return null;
-        }
-        return new ExtractedAnnotationVisitor(annotationParsingContext(descriptor), parent::accept);
+      if (readExtracted && descriptor.equals(ExtractedAnnotations.DESCRIPTOR)) {
+        return new ExtractedAnnotationsVisitor(
+            annotationParsingContext(descriptor), parent::accept);
       }
-      if (onlyExtracted) {
-        // When reading extracted edges ignore all non-extracted annotations.
+      if (!readEmbedded) {
         return null;
       }
       if (descriptor.equals(Edge.DESCRIPTOR)) {
@@ -294,13 +294,21 @@
     @Override
     public MethodVisitor visitMethod(
         int access, String name, String descriptor, String signature, String[] exceptions) {
-      return new KeepEdgeMethodVisitor(parsingContext, parent::accept, className, name, descriptor);
+      if (readEmbedded) {
+        return new KeepEdgeMethodVisitor(
+            parsingContext, parent::accept, className, name, descriptor);
+      }
+      return null;
     }
 
     @Override
     public FieldVisitor visitField(
         int access, String name, String descriptor, String signature, Object value) {
-      return new KeepEdgeFieldVisitor(parsingContext, parent::accept, className, name, descriptor);
+      if (readEmbedded) {
+        return new KeepEdgeFieldVisitor(
+            parsingContext, parent::accept, className, name, descriptor);
+      }
+      return null;
     }
   }
 
@@ -521,12 +529,54 @@
     }
   }
 
+  private static class ExtractedAnnotationsVisitor extends AnnotationVisitorBase {
+
+    private final Parent<KeepDeclaration> parent;
+    private List<KeepDeclaration> declarations = new ArrayList<>();
+
+    public ExtractedAnnotationsVisitor(
+        AnnotationParsingContext parsingContext, Parent<KeepDeclaration> parent) {
+      super(parsingContext);
+      this.parent = parent;
+    }
+
+    @Override
+    public AnnotationVisitor visitArray(String name) {
+      if (name.equals(ExtractedAnnotations.value)) {
+        PropertyParsingContext parsingContext = getParsingContext().property(name);
+        return new AnnotationVisitorBase(parsingContext) {
+          @Override
+          public AnnotationVisitor visitAnnotation(String nullName, String descriptor) {
+            assert nullName == null;
+            if (descriptor.equals(ExtractedAnnotation.DESCRIPTOR)) {
+              return new ExtractedAnnotationVisitor(
+                  parsingContext.annotation(descriptor), declarations::add);
+            }
+            return super.visitAnnotation(nullName, descriptor);
+          }
+        };
+      }
+      return super.visitArray(name);
+    }
+
+    @Override
+    public void visitEnd() {
+      if (declarations.isEmpty()) {
+        throw new KeepEdgeException("Invalid extracted annotation set, expected non-empty.");
+      }
+      declarations.forEach(parent::accept);
+      super.visitEnd();
+    }
+  }
+
   private static class ExtractedAnnotationVisitor extends AnnotationVisitorBase {
 
     private final Parent<KeepDeclaration> parent;
-    private String context = null;
+    private ContextDescriptor context = null;
     private String version = null;
-    private List<KeepEdgeVisitor> edgeVisitors = new ArrayList<>();
+    private KeepEdgeVisitor edgeVisitor = null;
+    private boolean isCheckRemoved = false;
+    private boolean isCheckOptimizedOut = false;
 
     public ExtractedAnnotationVisitor(
         AnnotationParsingContext parsingContext, Parent<KeepDeclaration> parent) {
@@ -542,61 +592,78 @@
 
     @Override
     public void visit(String name, Object value) {
-      if (name.equals(Extracted.version) && value instanceof String) {
+      if (name.equals(ExtractedAnnotation.version) && value instanceof String) {
         version = (String) value;
         return;
       }
-      ensureVersion(getParsingContext().property(name));
-      if (name.equals(Extracted.context) && value instanceof String) {
-        context = (String) value;
+      ParsingContext parsingContext = getParsingContext().property(name);
+      ensureVersion(parsingContext);
+      if (name.equals(ExtractedAnnotation.context) && value instanceof String) {
+        context = ContextDescriptor.parse((String) value, parsingContext);
+        return;
+      }
+      if (name.equals(ExtractedAnnotation.checkRemoved) && value instanceof Boolean) {
+        isCheckRemoved = true;
+        return;
+      }
+      if (name.equals(ExtractedAnnotation.checkOptimizedOut) && value instanceof Boolean) {
+        isCheckOptimizedOut = true;
         return;
       }
       super.visit(name, value);
     }
 
     @Override
-    public AnnotationVisitor visitArray(String name) {
-      if (name.equals(Extracted.edges)) {
-        PropertyParsingContext parsingContext = getParsingContext().property(name);
-        ensureVersion(parsingContext);
-        return new AnnotationVisitorBase(parsingContext) {
-          @Override
-          public AnnotationVisitor visitAnnotation(String nullName, String descriptor) {
-            assert nullName == null;
-            if (descriptor.equals(Edge.DESCRIPTOR)) {
-              KeepEdgeVisitor visitor =
-                  new KeepEdgeVisitor(
-                      parsingContext.annotation(descriptor), edge -> {}, builder -> {});
-              edgeVisitors.add(visitor);
-              return visitor;
-            }
-            return super.visitAnnotation(nullName, descriptor);
-          }
-        };
+    public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+      if (name.equals(ExtractedAnnotation.edge) && descriptor.equals(Edge.DESCRIPTOR)) {
+        edgeVisitor =
+            new KeepEdgeVisitor(
+                getParsingContext().annotation(descriptor), edge -> {}, builder -> {});
+        return edgeVisitor;
       }
-      return super.visitArray(name);
+      return super.visitAnnotation(name, descriptor);
     }
 
     @Override
     public void visitEnd() {
       if (version == null) {
-        throw new KeepEdgeException("Invalid extracted edge, expected a version property.");
+        throw getParsingContext()
+            .error("Invalid extracted annotation, expected a version property.");
       }
       if (context == null) {
-        throw new KeepEdgeException("Invalid extracted edge, expected a context property.");
+        throw getParsingContext()
+            .error("Invalid extracted annotation, expected a context property.");
       }
-      for (KeepEdgeVisitor visitor : edgeVisitors) {
+      if (edgeVisitor != null) {
+        if (isCheckRemoved || isCheckOptimizedOut) {
+          throw getParsingContext()
+              .error("Invalid extracted annotation, cannot be both an edge and check.");
+        }
         parent.accept(
-            visitor
+            edgeVisitor
                 .builder
-                .setMetaInfo(
-                    visitor
-                        .metaInfoBuilder
-                        // TODO(b/323815449): This may be a method or field descriptor!
-                        .setContextFromClassDescriptor(context)
-                        .build())
+                .setMetaInfo(context.applyToMetadata(edgeVisitor.metaInfoBuilder).build())
                 .build());
+        return;
       }
+      if (isCheckRemoved && isCheckOptimizedOut) {
+        throw getParsingContext()
+            .error(
+                "Invalid extracted annotation, cannot be both a removed and optimized-out check.");
+      }
+      if (!isCheckRemoved && !isCheckOptimizedOut) {
+        throw getParsingContext()
+            .error(
+                "Invalid extracted annotation, must specify either an edge, a removed check, or an"
+                    + " optimized-out check.");
+      }
+      KeepCheckKind kind = isCheckRemoved ? KeepCheckKind.REMOVED : KeepCheckKind.OPTIMIZED_OUT;
+      parent.accept(
+          KeepCheck.builder()
+              .setMetaInfo(context.applyToMetadata(KeepEdgeMetaInfo.builder()).build())
+              .setKind(kind)
+              .setItemPattern(context.toItemPattern())
+              .build());
       super.visitEnd();
     }
   }
@@ -1371,6 +1438,7 @@
           Item.classAnnotatedByClassNamePattern, ClassNameProperty.PATTERN);
 
       instanceOfParser = new InstanceOfParser(parsingContext);
+      instanceOfParser.setProperty(Item.instanceOfPattern, InstanceOfProperties.PATTERN);
       instanceOfParser.setProperty(Item.instanceOfClassName, InstanceOfProperties.NAME);
       instanceOfParser.setProperty(Item.instanceOfClassConstant, InstanceOfProperties.CONSTANT);
       instanceOfParser.setProperty(
@@ -2173,4 +2241,5 @@
       }
     }
   }
+
 }
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 6774605..a0a98c7 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
@@ -4,34 +4,63 @@
 package com.android.tools.r8.keepanno.asm;
 
 import com.android.tools.r8.keepanno.ast.AccessVisibility;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.AnnotationPattern;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.Binding;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.ClassNamePattern;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Condition;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.Constraints;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants.Extracted;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ExtractedAnnotation;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ExtractedAnnotations;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.FieldAccess;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.InstanceOfPattern;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Item;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.Kind;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.MemberAccess;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.MethodAccess;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.StringPattern;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.TypePattern;
+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.KeepClassItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.keepanno.ast.KeepConstraint;
+import com.android.tools.r8.keepanno.ast.KeepConstraints;
+import com.android.tools.r8.keepanno.ast.KeepDeclaration;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.ast.KeepEdgeException;
 import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
+import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
+import com.android.tools.r8.keepanno.ast.KeepInstanceOfPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepItemReference;
 import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
 import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
 import com.android.tools.r8.keepanno.ast.KeepPreconditions;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepStringPattern;
+import com.android.tools.r8.keepanno.ast.KeepTarget;
 import com.android.tools.r8.keepanno.ast.KeepTypePattern;
 import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.ModifierPattern;
 import com.android.tools.r8.keepanno.ast.OptionalPattern;
 import com.android.tools.r8.keepanno.utils.Unimplemented;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import org.objectweb.asm.AnnotationVisitor;
@@ -97,40 +126,106 @@
     visitor.visitEnd();
   }
 
-  public static void writeExtractedEdge(
-      KeepEdge edge, BiFunction<String, Boolean, AnnotationVisitorInterface> getVisitor) {
+  public static void writeExtractedEdges(
+      List<KeepDeclaration> declarations,
+      BiFunction<String, Boolean, AnnotationVisitorInterface> getVisitor) {
+    if (declarations.isEmpty()) {
+      return;
+    }
     withNewVisitor(
-        wrap(getVisitor.apply(Extracted.DESCRIPTOR, false)),
-        extractVisitor -> {
-          extractVisitor.visit("version", edge.getMetaInfo().getVersion().toVersionString());
-          extractVisitor.visit("context", edge.getMetaInfo().getContextDescriptorString());
+        wrap(getVisitor.apply(ExtractedAnnotations.DESCRIPTOR, false)),
+        containerVisitor ->
+            withNewVisitor(
+                containerVisitor.visitArray(ExtractedAnnotations.value),
+                arrayVisitor ->
+                    declarations.forEach(
+                        decl ->
+                            withNewVisitor(
+                                arrayVisitor.visitAnnotation(null, ExtractedAnnotation.DESCRIPTOR),
+                                extractVisitor -> writeExtractedEdge(extractVisitor, decl)))));
+  }
+
+  private static void writeExtractedEdge(AnnotationVisitor visitor, KeepDeclaration decl) {
+    KeepEdgeMetaInfo metaInfo = decl.getMetaInfo();
+    visitor.visit(ExtractedAnnotation.version, metaInfo.getVersion().toVersionString());
+    visitor.visit(ExtractedAnnotation.context, metaInfo.getContextDescriptorString());
+    decl.match(
+        edge -> {
           withNewVisitor(
-              extractVisitor.visitArray("edges"),
-              edgeVisitor ->
-                  writeEdgeInternal(
-                      edge, (desc, visible) -> edgeVisitor.visitAnnotation(null, desc)));
+              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;
         });
   }
 
-  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) {
-    withNewVisitor(
-        getVisitor.apply(Edge.DESCRIPTOR, false),
-        visitor -> new KeepEdgeWriter().writeEdge(edge, visitor));
-  }
-
   private void writeEdge(KeepEdge edge, AnnotationVisitor visitor) {
     writeMetaInfo(visitor, edge.getMetaInfo());
+    writeBindings(visitor, edge.getBindings());
     writePreconditions(visitor, edge.getPreconditions());
     writeConsequences(visitor, edge.getConsequences());
   }
 
-  private void writeMetaInfo(AnnotationVisitor visitor, KeepEdgeMetaInfo metaInfo) {}
+  private void writeMetaInfo(AnnotationVisitor visitor, KeepEdgeMetaInfo metaInfo) {
+    // The edge version and context is written in the extraction header.
+    if (metaInfo.hasDescription()) {
+      visitor.visit(Edge.description, metaInfo.getDescriptionString());
+    }
+  }
+
+  private void writeBindings(AnnotationVisitor visitor, KeepBindings bindings) {
+    if (bindings.isEmpty()) {
+      return;
+    }
+    withNewVisitor(
+        visitor.visitArray(Edge.bindings),
+        arrayVisitor -> {
+          bindings.forEach(
+              (symbol, item) -> {
+                withNewVisitor(
+                    arrayVisitor.visitAnnotation(null, Binding.DESCRIPTOR),
+                    bindingVisitor -> {
+                      bindingVisitor.visit(Binding.bindingName, symbol.toString());
+                      // The item is written directly into the binding annotation.
+                      writeItem(bindingVisitor, item);
+                    });
+              });
+        });
+  }
+
+  private void writeStringPattern(
+      KeepStringPattern stringPattern, String propertyName, AnnotationVisitor visitor) {
+    withNewVisitor(
+        visitor.visitAnnotation(propertyName, AnnotationConstants.StringPattern.DESCRIPTOR),
+        v -> {
+          if (stringPattern.isAny()) {
+            // The emtpy pattern matches any string.
+            return;
+          }
+          if (stringPattern.isExact()) {
+            v.visit(StringPattern.exact, stringPattern.asExactString());
+            return;
+          }
+          if (stringPattern.hasPrefix()) {
+            v.visit(StringPattern.startsWith, stringPattern.getPrefixString());
+          }
+          if (stringPattern.hasSuffix()) {
+            v.visit(StringPattern.endsWith, stringPattern.getSuffixString());
+          }
+        });
+  }
 
   private void writePreconditions(AnnotationVisitor visitor, KeepPreconditions preconditions) {
     if (preconditions.isAlways()) {
@@ -144,12 +239,8 @@
                 condition ->
                     withNewVisitor(
                         arrayVisitor.visitAnnotation(ignoredArrayValueName, Condition.DESCRIPTOR),
-                        conditionVisitor -> {
-                          if (condition.getItem().isBindingReference()) {
-                            throw new Unimplemented();
-                          }
-                          writeItem(conditionVisitor, condition.getItem().asItemPattern());
-                        })));
+                        conditionVisitor ->
+                            writeItemReference(conditionVisitor, condition.getItem()))));
   }
 
   private void writeConsequences(AnnotationVisitor visitor, KeepConsequences consequences) {
@@ -162,12 +253,105 @@
                 target ->
                     withNewVisitor(
                         arrayVisitor.visitAnnotation(ignoredArrayValueName, Target.DESCRIPTOR),
-                        targetVisitor -> {
-                          if (target.getItem().isBindingReference()) {
-                            throw new Unimplemented();
-                          }
-                          writeItem(targetVisitor, target.getItem().asItemPattern());
-                        })));
+                        targetVisitor -> writeTarget(target, targetVisitor))));
+  }
+
+  private void writeTarget(KeepTarget target, AnnotationVisitor visitor) {
+    writeConstraints(visitor, target.getConstraints(), target.getItem());
+    writeItemReference(visitor, target.getItem());
+  }
+
+  private void writeConstraints(
+      AnnotationVisitor visitor, KeepConstraints constraints, KeepItemReference item) {
+    Set<KeepConstraint> typedConstraints;
+    if (item.isClassItemReference()) {
+      typedConstraints = constraints.getClassConstraints();
+    } else if (item.isMemberItemPattern()) {
+      KeepMemberPattern memberPattern = item.asMemberItemPattern().getMemberPattern();
+      if (memberPattern.isMethod()) {
+        typedConstraints = constraints.getMethodConstraints();
+      } else if (memberPattern.isField()) {
+        typedConstraints = constraints.getFieldConstraints();
+      } else {
+        typedConstraints = constraints.getMemberConstraints();
+      }
+    } else {
+      typedConstraints = constraints.getMemberConstraints();
+    }
+
+    List<String> constraintEnumValues = new ArrayList<>();
+    List<KeepAnnotationPattern> annotationConstraints = new ArrayList<>();
+    for (KeepConstraint constraint : typedConstraints) {
+      String value = constraint.getEnumValue();
+      if (value != null) {
+        constraintEnumValues.add(value);
+        continue;
+      }
+      KeepAnnotationPattern annotationPattern = constraint.asAnnotationPattern();
+      if (annotationPattern != null) {
+        annotationConstraints.add(annotationPattern);
+        continue;
+      }
+      throw new Unimplemented("Missing: " + constraint.getClass().toString());
+    }
+    // The default constraints is *not* the empty set, so always write it as defined.
+    constraintEnumValues.sort(String::compareTo);
+    withNewVisitor(
+        visitor.visitArray(Target.constraints),
+        arrayVisitor ->
+            constraintEnumValues.forEach(
+                c -> arrayVisitor.visitEnum(null, Constraints.DESCRIPTOR, c)));
+    if (!annotationConstraints.isEmpty()) {
+      if (annotationConstraints.size() > 1) {
+        annotationConstraints.sort(
+            Comparator.comparing((KeepAnnotationPattern p) -> p.getNamePattern().toString())
+                .thenComparingInt(p -> p.includesClassRetention() ? 1 : 0)
+                .thenComparingInt(p -> p.includesRuntimeRetention() ? 1 : 0));
+      }
+      withNewVisitor(
+          visitor.visitArray(Target.constrainAnnotations),
+          arrayVisitor ->
+              annotationConstraints.forEach(
+                  annotation -> {
+                    withNewVisitor(
+                        arrayVisitor.visitAnnotation(null, AnnotationPattern.DESCRIPTOR),
+                        annoVisitor -> {
+                          writeClassNamePattern(
+                              annotation.getNamePattern(),
+                              AnnotationPattern.namePattern,
+                              annoVisitor);
+                          assert annotation.includesClassRetention()
+                              || annotation.includesRuntimeRetention();
+                          withNewVisitor(
+                              annoVisitor.visitArray(AnnotationPattern.retention),
+                              retentionArrayVisitor -> {
+                                if (annotation.includesClassRetention()) {
+                                  retentionArrayVisitor.visitEnum(
+                                      null,
+                                      "Ljava/lang/annotation/RetentionPolicy;",
+                                      RetentionPolicy.CLASS.name());
+                                }
+                                if (annotation.includesRuntimeRetention()) {
+                                  retentionArrayVisitor.visitEnum(
+                                      null,
+                                      "Ljava/lang/annotation/RetentionPolicy;",
+                                      RetentionPolicy.RUNTIME.name());
+                                }
+                              });
+                        });
+                  }));
+    }
+  }
+
+  private void writeItemReference(AnnotationVisitor visitor, KeepItemReference itemReference) {
+    if (itemReference.isBindingReference()) {
+      KeepBindingReference bindingReference = itemReference.asBindingReference();
+      String bindingProperty =
+          bindingReference.isClassType() ? Item.classFromBinding : Item.memberFromBinding;
+      visitor.visit(bindingProperty, bindingReference.getName().toString());
+    } else {
+      writeItem(visitor, itemReference.asItemPattern());
+    }
   }
 
   private void writeItem(AnnotationVisitor itemVisitor, KeepItemPattern item) {
@@ -186,25 +370,40 @@
         itemVisitor);
     writeClassNamePattern(
         classItemPattern.getClassNamePattern(), Item.classNamePattern, itemVisitor);
-    if (!classItemPattern.getInstanceOfPattern().isAny()) {
-      throw new Unimplemented();
+    writeInstanceOfPattern(classItemPattern.getInstanceOfPattern(), itemVisitor);
+  }
+
+  private void writeInstanceOfPattern(
+      KeepInstanceOfPattern instanceOfPattern, AnnotationVisitor visitor) {
+    if (instanceOfPattern.isAny()) {
+      return;
     }
+    withNewVisitor(
+        visitor.visitAnnotation(Item.instanceOfPattern, InstanceOfPattern.DESCRIPTOR),
+        v -> {
+          v.visit(InstanceOfPattern.inclusive, instanceOfPattern.isInclusive());
+          writeClassNamePattern(
+              instanceOfPattern.getClassNamePattern(), InstanceOfPattern.classNamePattern, v);
+        });
   }
 
   private void writeMemberItem(
       KeepMemberItemPattern memberItemPattern, AnnotationVisitor itemVisitor) {
-    if (memberItemPattern.getClassReference().isBindingReference()) {
-      throw new Unimplemented();
+    KeepClassItemReference classReference = memberItemPattern.getClassReference();
+    if (classReference.isBindingReference()) {
+      KeepBindingReference bindingReference = classReference.asBindingReference();
+      itemVisitor.visit(Item.classFromBinding, bindingReference.getName().toString());
+    } else {
+      writeClassItem(classReference.asClassItemPattern(), itemVisitor);
     }
-    writeClassItem(memberItemPattern.getClassReference().asClassItemPattern(), itemVisitor);
     writeMember(memberItemPattern.getMemberPattern(), itemVisitor);
   }
 
   private void writeMember(KeepMemberPattern memberPattern, AnnotationVisitor targetVisitor) {
     if (memberPattern.isAllMembers()) {
-      throw new Unimplemented();
-    }
-    if (memberPattern.isMethod()) {
+      // Due to the empty default being a class, we need to set the kind to members.
+      targetVisitor.visitEnum(Target.kind, Kind.DESCRIPTOR, Kind.ONLY_MEMBERS);
+    } else if (memberPattern.isMethod()) {
       writeMethod(memberPattern.asMethod(), targetVisitor);
     } else if (memberPattern.isField()) {
       writeField(memberPattern.asField(), targetVisitor);
@@ -219,34 +418,26 @@
     assert !member.isMethod();
     writeAnnotatedBy(
         Item.memberAnnotatedByClassNamePattern, member.getAnnotatedByPattern(), targetVisitor);
-    writeAccessPattern(Item.memberAccess, member.getAccessPattern(), targetVisitor);
+    writeGeneralMemberAccessPattern(Item.memberAccess, member.getAccessPattern(), targetVisitor);
   }
 
   private void writeField(KeepFieldPattern field, AnnotationVisitor targetVisitor) {
-    String exactFieldName = field.getNamePattern().asExactString();
-    if (exactFieldName != null) {
-      targetVisitor.visit(Item.fieldName, exactFieldName);
-    } else {
-      throw new Unimplemented();
-    }
-    if (!field.getAccessPattern().isAny()) {
-      throw new Unimplemented();
-    }
+    writeAnnotatedBy(
+        Item.fieldAnnotatedByClassNamePattern, field.getAnnotatedByPattern(), targetVisitor);
+    writeFieldAccessPattern(Item.fieldAccess, field.getAccessPattern(), targetVisitor);
+    writeStringPattern(
+        field.getNamePattern().asStringPattern(), Item.fieldNamePattern, targetVisitor);
     if (!field.getTypePattern().isAny()) {
-      throw new Unimplemented();
+      writeTypePattern(field.getTypePattern().asType(), Item.fieldTypePattern, targetVisitor);
     }
   }
 
   private void writeMethod(KeepMethodPattern method, AnnotationVisitor targetVisitor) {
-    String exactMethodName = method.getNamePattern().asExactString();
-    if (exactMethodName != null) {
-      targetVisitor.visit(Item.methodName, exactMethodName);
-    } else {
-      throw new Unimplemented();
-    }
-    if (!method.getAccessPattern().isAny()) {
-      throw new Unimplemented();
-    }
+    writeAnnotatedBy(
+        Item.methodAnnotatedByClassNamePattern, method.getAnnotatedByPattern(), targetVisitor);
+    writeMethodAccessPattern(Item.methodAccess, method.getAccessPattern(), targetVisitor);
+    writeStringPattern(
+        method.getNamePattern().asStringPattern(), Item.methodNamePattern, targetVisitor);
     writeMethodReturnType(method.getReturnTypePattern(), targetVisitor);
     writeMethodParameters(method.getParametersPattern(), targetVisitor);
   }
@@ -343,13 +534,65 @@
     }
     if (pattern.isOnlyPositive()) {
       arrayVisitor.visitEnum(null, desc, value);
+      return;
     }
     assert pattern.isOnlyNegative();
     arrayVisitor.visitEnum(null, desc, MemberAccess.NEGATION_PREFIX + value);
   }
 
-  private void writeAccessPattern(
+  private void writeGeneralMemberAccessPattern(
       String propertyName, KeepMemberAccessPattern accessPattern, AnnotationVisitor targetVisitor) {
+    internalWriteAccessPattern(
+        propertyName, accessPattern, targetVisitor, MemberAccess.DESCRIPTOR, v -> {});
+  }
+
+  private void writeFieldAccessPattern(
+      String propertyName, KeepFieldAccessPattern accessPattern, AnnotationVisitor targetVisitor) {
+    String enumDescriptor = FieldAccess.DESCRIPTOR;
+    internalWriteAccessPattern(
+        propertyName,
+        accessPattern,
+        targetVisitor,
+        enumDescriptor,
+        visitor -> {
+          writeModifierEnumValue(
+              accessPattern.getVolatilePattern(), FieldAccess.VOLATILE, enumDescriptor, visitor);
+          writeModifierEnumValue(
+              accessPattern.getTransientPattern(), FieldAccess.TRANSIENT, enumDescriptor, visitor);
+        });
+  }
+
+  private void writeMethodAccessPattern(
+      String propertyName, KeepMethodAccessPattern accessPattern, AnnotationVisitor targetVisitor) {
+    String enumDescriptor = MethodAccess.DESCRIPTOR;
+    internalWriteAccessPattern(
+        propertyName,
+        accessPattern,
+        targetVisitor,
+        enumDescriptor,
+        visitor -> {
+          writeModifierEnumValue(
+              accessPattern.getSynchronizedPattern(),
+              MethodAccess.SYNCHRONIZED,
+              enumDescriptor,
+              visitor);
+          writeModifierEnumValue(
+              accessPattern.getBridgePattern(), MethodAccess.BRIDGE, enumDescriptor, visitor);
+          writeModifierEnumValue(
+              accessPattern.getNativePattern(), MethodAccess.NATIVE, enumDescriptor, visitor);
+          writeModifierEnumValue(
+              accessPattern.getAbstractPattern(), MethodAccess.ABSTRACT, enumDescriptor, visitor);
+          writeModifierEnumValue(
+              accessPattern.getStrictFpPattern(), MethodAccess.STRICT_FP, enumDescriptor, visitor);
+        });
+  }
+
+  private void internalWriteAccessPattern(
+      String propertyName,
+      KeepMemberAccessPattern accessPattern,
+      AnnotationVisitor targetVisitor,
+      String enumDescriptor,
+      Consumer<AnnotationVisitor> typeSpecificSettings) {
     if (accessPattern.isAny()) {
       return;
     }
@@ -360,35 +603,28 @@
             for (AccessVisibility visibility : accessPattern.getAllowedAccessVisibilities()) {
               switch (visibility) {
                 case PUBLIC:
-                  visitor.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PUBLIC);
+                  visitor.visitEnum(null, enumDescriptor, MemberAccess.PUBLIC);
                   break;
                 case PROTECTED:
-                  visitor.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PROTECTED);
+                  visitor.visitEnum(null, enumDescriptor, MemberAccess.PROTECTED);
                   break;
                 case PACKAGE_PRIVATE:
-                  visitor.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PACKAGE_PRIVATE);
+                  visitor.visitEnum(null, enumDescriptor, MemberAccess.PACKAGE_PRIVATE);
                   break;
                 case PRIVATE:
-                  visitor.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PRIVATE);
+                  visitor.visitEnum(null, enumDescriptor, MemberAccess.PRIVATE);
                   break;
               }
             }
           }
           writeModifierEnumValue(
-              accessPattern.getStaticPattern(),
-              MemberAccess.STATIC,
-              MemberAccess.DESCRIPTOR,
-              visitor);
+              accessPattern.getStaticPattern(), MemberAccess.STATIC, enumDescriptor, visitor);
           writeModifierEnumValue(
-              accessPattern.getFinalPattern(),
-              MemberAccess.FINAL,
-              MemberAccess.DESCRIPTOR,
-              visitor);
+              accessPattern.getFinalPattern(), MemberAccess.FINAL, enumDescriptor, visitor);
           writeModifierEnumValue(
-              accessPattern.getSyntheticPattern(),
-              MemberAccess.SYNTHETIC,
-              MemberAccess.DESCRIPTOR,
-              visitor);
+              accessPattern.getSyntheticPattern(), MemberAccess.SYNTHETIC, enumDescriptor, visitor);
+
+          typeSpecificSettings.accept(visitor);
         });
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index 0789197..81ccbc6 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -15,12 +15,21 @@
  * annotations which overlap in name with the actual semantic AST types.
  */
 public final class AnnotationConstants {
-  public static final class Extracted {
+  public static final class ExtractedAnnotations {
     public static final String DESCRIPTOR =
         "Lcom/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations;";
+    public static final String value = "value";
+  }
+
+  public static final class ExtractedAnnotation {
+    public static final String DESCRIPTOR =
+        "Lcom/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation;";
     public static final String version = "version";
     public static final String context = "context";
-    public static final String edges = "edges";
+    public static final String keepAnnotationGroup = "keep-annotation";
+    public static final String edge = "edge";
+    public static final String checkRemoved = "checkRemoved";
+    public static final String checkOptimizedOut = "checkOptimizedOut";
   }
 
   public static final class Edge {
@@ -87,6 +96,7 @@
     public static final String instanceOfClassConstant = "instanceOfClassConstant";
     public static final String instanceOfClassConstantExclusive =
         "instanceOfClassConstantExclusive";
+    public static final String instanceOfPattern = "instanceOfPattern";
     public static final String classAnnotatedByGroup = "class-annotated-by";
     public static final String classAnnotatedByClassName = "classAnnotatedByClassName";
     public static final String classAnnotatedByClassConstant = "classAnnotatedByClassConstant";
@@ -231,10 +241,20 @@
   public static final class ClassNamePattern {
     public static final String DESCRIPTOR =
         "Lcom/android/tools/r8/keepanno/annotations/ClassNamePattern;";
+    public static final String classNameGroup = "class-name";
+    public static final String name = "name";
+    public static final String constant = "constant";
     public static final String simpleName = "simpleName";
     public static final String packageName = "packageName";
   }
 
+  public static final class InstanceOfPattern {
+    public static final String DESCRIPTOR =
+        "Lcom/android/tools/r8/keepanno/annotations/InstanceOfPattern;";
+    public static final String inclusive = "inclusive";
+    public static final String classNamePattern = "classNamePattern";
+  }
+
   public static final class AnnotationPattern {
     public static final String DESCRIPTOR =
         "Lcom/android/tools/r8/keepanno/annotations/AnnotationPattern;";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
index 454f725..24ec53d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.Constraints;
 import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
 import java.util.Set;
 
@@ -24,6 +25,12 @@
     return System.identityHashCode(this);
   }
 
+  public abstract String getEnumValue();
+
+  public KeepAnnotationPattern asAnnotationPattern() {
+    return null;
+  }
+
   public abstract void convertToDisallowKeepOptions(KeepOptions.Builder builder);
 
   public void addRequiredKeepAttributes(Set<KeepAttribute> attributes) {
@@ -65,6 +72,11 @@
     private Lookup() {}
 
     @Override
+    public String getEnumValue() {
+      return Constraints.LOOKUP;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.SHRINKING);
     }
@@ -81,6 +93,11 @@
     private Name() {}
 
     @Override
+    public String getEnumValue() {
+      return Constraints.NAME;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.OBFUSCATING);
     }
@@ -97,6 +114,11 @@
     private VisibilityRelax() {}
 
     @Override
+    public String getEnumValue() {
+      return Constraints.VISIBILITY_RELAX;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       // The compiler currently satisfies that access is never restricted.
     }
@@ -113,6 +135,11 @@
     private VisibilityRestrict() {}
 
     @Override
+    public String getEnumValue() {
+      return Constraints.VISIBILITY_RESTRICT;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       // We don't have directional rules so this prohibits any modification.
       builder.add(KeepOption.ACCESS_MODIFICATION);
@@ -130,6 +157,11 @@
     private NeverInline() {}
 
     @Override
+    public String getEnumValue() {
+      return Constraints.NEVER_INLINE;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.OPTIMIZING);
     }
@@ -151,6 +183,11 @@
     }
 
     @Override
+    public String getEnumValue() {
+      return Constraints.CLASS_INSTANTIATE;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.OPTIMIZING);
     }
@@ -172,6 +209,11 @@
     }
 
     @Override
+    public String getEnumValue() {
+      return Constraints.CLASS_OPEN_HIERARCHY;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.OPTIMIZING);
     }
@@ -193,6 +235,11 @@
     }
 
     @Override
+    public String getEnumValue() {
+      return Constraints.METHOD_INVOKE;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.OPTIMIZING);
     }
@@ -214,6 +261,11 @@
     }
 
     @Override
+    public String getEnumValue() {
+      return Constraints.METHOD_REPLACE;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.OPTIMIZING);
     }
@@ -235,6 +287,11 @@
     }
 
     @Override
+    public String getEnumValue() {
+      return Constraints.FIELD_GET;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.OPTIMIZING);
     }
@@ -256,6 +313,11 @@
     }
 
     @Override
+    public String getEnumValue() {
+      return Constraints.FIELD_SET;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.OPTIMIZING);
     }
@@ -277,6 +339,11 @@
     }
 
     @Override
+    public String getEnumValue() {
+      return Constraints.FIELD_REPLACE;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       builder.add(KeepOption.OPTIMIZING);
     }
@@ -325,6 +392,17 @@
     }
 
     @Override
+    public KeepAnnotationPattern asAnnotationPattern() {
+      return annotationPattern;
+    }
+
+    @Override
+    public String getEnumValue() {
+      // The annotation constraints cannot be represented by an enum value.
+      return null;
+    }
+
+    @Override
     public void convertToDisallowKeepOptions(KeepOptions.Builder builder) {
       // The annotation constraint only implies that annotations should remain, no restrictions
       // are on the item otherwise. Also, we can't restrict the rule to just the annotations being
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
index eb5cc2e..746d22a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
@@ -11,6 +11,7 @@
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 public abstract class KeepConstraints {
@@ -70,11 +71,50 @@
     }
   }
 
+  abstract Set<KeepConstraint> getConstraints();
+
+  private Set<KeepConstraint> getConstraintsMatching(Predicate<KeepConstraint> predicate) {
+    ImmutableSet.Builder<KeepConstraint> builder = ImmutableSet.builder();
+    for (KeepConstraint constraint : getConstraints()) {
+      if (predicate.test(constraint)) {
+        builder.add(constraint);
+      }
+    }
+    return builder.build();
+  }
+
+  public final Set<KeepConstraint> getClassConstraints() {
+    return getConstraintsMatching(KeepConstraint::validForClass);
+  }
+
+  public final Set<KeepConstraint> getMethodConstraints() {
+    return getConstraintsMatching(KeepConstraint::validForMethod);
+  }
+
+  public final Set<KeepConstraint> getFieldConstraints() {
+    return getConstraintsMatching(KeepConstraint::validForField);
+  }
+
+  public final Set<KeepConstraint> getMemberConstraints() {
+    return getConstraintsMatching(c -> c.validForMethod() || c.validForField());
+  }
+
   private static class Defaults extends KeepConstraints {
 
     private static final Defaults INSTANCE = new Defaults();
 
     @Override
+    Set<KeepConstraint> getConstraints() {
+      return ImmutableSet.of(
+          KeepConstraint.lookup(),
+          KeepConstraint.name(),
+          KeepConstraint.classInstantiate(),
+          KeepConstraint.methodInvoke(),
+          KeepConstraint.fieldGet(),
+          KeepConstraint.fieldSet());
+    }
+
+    @Override
     public KeepOptions convertToKeepOptions(KeepOptions defaultOptions) {
       return defaultOptions;
     }
@@ -100,6 +140,14 @@
     }
 
     @Override
+    Set<KeepConstraint> getConstraints() {
+      return ImmutableSet.<KeepConstraint>builder()
+          .addAll(Defaults.INSTANCE.getConstraints())
+          .addAll(additions.getConstraints())
+          .build();
+    }
+
+    @Override
     public KeepOptions convertToKeepOptions(KeepOptions defaultOptions) {
       KeepOptions additionalOptions = additions.convertToKeepOptions(defaultOptions);
       KeepOptions.Builder builder = KeepOptions.disallowBuilder();
@@ -126,6 +174,11 @@
     }
 
     @Override
+    public Set<KeepConstraint> getConstraints() {
+      return constraints;
+    }
+
+    @Override
     public KeepOptions convertToKeepOptions(KeepOptions defaultOptions) {
       KeepOptions.Builder builder = KeepOptions.disallowBuilder();
       for (KeepConstraint constraint : constraints) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
index c6f6248..4425317 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
@@ -3,11 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+import java.util.function.Function;
+
 /** Base class for the declarations represented in the keep annoations library. */
 public abstract class KeepDeclaration {
 
   public abstract KeepEdgeMetaInfo getMetaInfo();
 
+  public final <T> T match(Function<KeepEdge, T> onEdge, Function<KeepCheck, T> onCheck) {
+    if (isKeepEdge()) {
+      return onEdge.apply(asKeepEdge());
+    }
+    return onCheck.apply(asKeepCheck());
+  }
+
   public final boolean isKeepEdge() {
     return asKeepEdge() != null;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 7ade086..91d36c7 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -25,7 +25,7 @@
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 import kotlin.Metadata;
-import kotlinx.metadata.jvm.JvmMetadataVersion;
+import kotlinx.metadata.InconsistentKotlinMetadataException;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 import kotlinx.metadata.jvm.KotlinClassMetadata.FileFacade;
 import kotlinx.metadata.jvm.KotlinClassMetadata.MultiFileClassFacade;
@@ -181,9 +181,9 @@
     Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue();
 
     try {
-      return KotlinClassMetadata.readStrict(
+      return KotlinClassMetadata.read(
           new KotlinMetadataAnnotationWrapper(k, mv, d1, d2, xs, pn, xi));
-    } catch (ClassCastException | IllegalArgumentException | MetadataError e) {
+    } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
       throw new KotlinMetadataException(e);
     }
   }
@@ -196,29 +196,15 @@
     return (Integer) kind.value.getBoxedValue();
   }
 
-  public static Metadata extractMetadataWithPossiblyUnsupportedMetadataVersion(
-      KotlinClassMetadata kMetadata) {
-    JvmMetadataVersion version = kMetadata.getVersion();
-    if (version.getMajor() == 1 && version.getMinor() < 4) {
-      // From version 0.8.0 the kotlin metadata library cannot write metadata below Kotlin version
-      // 1.4. The writer can be tricked by temporarily setting a high enough version.
-      kMetadata.setVersion(KotlinJvmMetadataVersionUtils.MIN_SUPPORTED_VERSION);
-      Metadata metadata = kMetadata.write();
-      kMetadata.setVersion(version);
-      return metadata;
-    }
-    return kMetadata.write();
-  }
-
   public static KotlinClassLevelInfo createKotlinInfo(
       Kotlin kotlin,
       DexClass clazz,
       KotlinClassMetadata kMetadata,
       AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode) {
-    Metadata metadata = extractMetadataWithPossiblyUnsupportedMetadataVersion(kMetadata);
+    Metadata metadata = kMetadata.getAnnotationData$kotlinx_metadata_jvm();
     String packageName = metadata.pn();
-    int[] metadataVersion = KotlinJvmMetadataVersionUtils.toIntArray(kMetadata.getVersion());
+    int[] metadataVersion = metadata.mv();
     if (kMetadata instanceof KotlinClassMetadata.Class) {
       return KotlinClassInfo.create(
           (KotlinClassMetadata.Class) kMetadata,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMetadataVersionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMetadataVersionUtils.java
deleted file mode 100644
index acf75d3..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMetadataVersionUtils.java
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin;
-
-import kotlinx.metadata.jvm.JvmMetadataVersion;
-
-public class KotlinJvmMetadataVersionUtils {
-
-  public static JvmMetadataVersion MIN_SUPPORTED_VERSION = new JvmMetadataVersion(1, 4, 0);
-
-  public static int[] toIntArray(JvmMetadataVersion version) {
-    return new int[] {version.getMajor(), version.getMinor(), version.getPatch()};
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataAnnotationWrapper.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataAnnotationWrapper.java
index 149fec9..d34a84f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataAnnotationWrapper.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataAnnotationWrapper.java
@@ -56,12 +56,10 @@
   }
 
   public static KotlinMetadataAnnotationWrapper wrap(KotlinClassMetadata classMetadata) {
-    Metadata annotationData =
-        KotlinClassMetadataReader.extractMetadataWithPossiblyUnsupportedMetadataVersion(
-            classMetadata);
+    Metadata annotationData = classMetadata.getAnnotationData$kotlinx_metadata_jvm();
     return new KotlinMetadataAnnotationWrapper(
         annotationData.k(),
-        KotlinJvmMetadataVersionUtils.toIntArray(classMetadata.getVersion()),
+        annotationData.mv(),
         annotationData.d1(),
         annotationData.d2(),
         annotationData.xs(),
diff --git a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
index 76c2314..9504a38 100644
--- a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
@@ -24,9 +24,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import kotlinx.metadata.jvm.JvmMetadataVersion;
-import kotlinx.metadata.jvm.KmModule;
-import kotlinx.metadata.jvm.KotlinModuleMetadata;
+import kotlinx.metadata.jvm.KotlinModuleMetadata.Writer;
 
 /**
  * The kotlin module synthesizer will scan through all file facades and multiclass files to figure
@@ -157,7 +155,7 @@
         }
       }
       Collections.sort(packagesSorted);
-      KmModule kmModule = new KmModule();
+      Writer writer = new Writer();
       for (String newPackage : packagesSorted) {
         // Calling other visitors than visitPackageParts are currently not supported.
         // https://github.com/JetBrains/kotlin/blob/master/libraries/kotlinx-metadata/
@@ -176,15 +174,14 @@
                             newMultiFiles.put(classPart, rewrittenName);
                           });
                 });
-        kmModule.visitPackageParts(
+        writer.visitPackageParts(
             newPackage,
             newFacades.getOrDefault(newPackage, Collections.emptyList()),
             newMultiFiles);
       }
       return Optional.of(
           DataEntryResource.fromBytes(
-              new KotlinModuleMetadata(kmModule, new JvmMetadataVersion(metadataVersion.get()))
-                  .write(),
+              writer.write(metadataVersion.get()),
               "META-INF/" + moduleName + ".kotlin_module",
               Origin.unknown()));
     }
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 cfe7d75..59915bc9 100644
--- a/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java
@@ -57,7 +57,6 @@
   public void testR8Native() throws Throwable {
     assumeTrue(parameters.isR8() && parameters.isNative());
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .applyIfR8Native(
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 a1a4a2c..b3bedf1 100644
--- a/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java
@@ -56,7 +56,6 @@
   public void testR8Native() throws Exception {
     assumeTrue(parameters.isR8() && parameters.isNative());
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .applyIfR8Native(
diff --git a/src/test/java/com/android/tools/r8/keepanno/FieldNameStringPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/FieldNameStringPatternsTest.java
index d804b85..d6a6b90 100644
--- a/src/test/java/com/android/tools/r8/keepanno/FieldNameStringPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/FieldNameStringPatternsTest.java
@@ -40,7 +40,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .setExcludedOuterClass(getClass())
         .run(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/keepanno/FieldPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/FieldPatternsTest.java
index 4c62801..669d945 100644
--- a/src/test/java/com/android/tools/r8/keepanno/FieldPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/FieldPatternsTest.java
@@ -38,7 +38,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java
index 4eec939..a9dec7d 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java
@@ -82,7 +82,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .allowAccessModification()
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 de359e4..53c7634 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoParameters.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoParameters.java
@@ -12,7 +12,8 @@
 
   public enum KeepAnnoConfig {
     REFERENCE,
-    R8_NATIVE,
+    R8_DIRECT,
+    R8_EXTRACT,
     R8_LEGACY,
     PG;
   }
@@ -55,7 +56,9 @@
   }
 
   public boolean isR8() {
-    return config == KeepAnnoConfig.R8_NATIVE || config == KeepAnnoConfig.R8_LEGACY;
+    return config == KeepAnnoConfig.R8_DIRECT
+        || config == KeepAnnoConfig.R8_EXTRACT
+        || config == KeepAnnoConfig.R8_LEGACY;
   }
 
   public boolean isPG() {
@@ -63,10 +66,12 @@
   }
 
   public boolean isNative() {
-    return config == KeepAnnoConfig.R8_NATIVE;
+    return config == KeepAnnoConfig.R8_DIRECT || config == KeepAnnoConfig.R8_EXTRACT;
   }
 
   public boolean isExtract() {
-    return config == KeepAnnoConfig.R8_LEGACY || config == KeepAnnoConfig.PG;
+    return config == KeepAnnoConfig.R8_EXTRACT
+        || 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 698e0bc..86d9529 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBase.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBase.java
@@ -20,7 +20,9 @@
       keepAnnoParams.add(
           new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.REFERENCE));
       keepAnnoParams.add(
-          new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_NATIVE));
+          new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_DIRECT));
+      keepAnnoParams.add(
+          new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_EXTRACT));
       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 1ec18af..e9d6b40 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -20,9 +20,8 @@
 import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
 import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
 import com.android.tools.r8.keepanno.ast.KeepDeclaration;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractorOptions;
-import com.android.tools.r8.keepanno.utils.Unimplemented;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.IOException;
@@ -41,8 +40,10 @@
     switch (params.config()) {
       case REFERENCE:
         return new ReferenceBuilder(params, temp);
-      case R8_NATIVE:
-        return new R8NativeBuilder(params, temp);
+      case R8_DIRECT:
+        return new R8NativeBuilder(false, params, temp);
+      case R8_EXTRACT:
+        return new R8NativeBuilder(true, params, temp);
       case R8_LEGACY:
         return new R8LegacyBuilder(params, temp);
       case PG:
@@ -120,10 +121,6 @@
     return inspectOutputConfig(System.out::println);
   }
 
-  public KeepAnnoTestBuilder skipEdgeExtraction() {
-    return this;
-  }
-
   public KeepAnnoTestBuilder inspectOutputConfig(Consumer<String> configConsumer) {
     // Default to ignore the consumer.
     return this;
@@ -166,24 +163,23 @@
 
     private final R8FullTestBuilder builder;
     private List<Consumer<R8TestCompileResult>> compileResultConsumers = new ArrayList<>();
-    private boolean useEdgeExtraction = true;
+    private final boolean useEdgeExtraction;
 
-    public R8NativeBuilder(KeepAnnoParameters params, TemporaryFolder temp) {
+    private R8NativeBuilder(
+        boolean useEdgeExtraction, KeepAnnoParameters params, TemporaryFolder temp) {
       super(params, temp);
       builder =
           TestBase.testForR8(temp, parameters().getBackend())
               .enableExperimentalKeepAnnotations()
               .setMinApi(parameters());
-      builder.getBuilder().setEnableExperimentalKeepAnnotations(false);
-      builder.getBuilder().setEnableExperimentalVersionedKeepEdgeAnnotations(true);
-    }
-
-    @Override
-    public KeepAnnoTestBuilder skipEdgeExtraction() {
-      useEdgeExtraction = false;
-      builder.getBuilder().setEnableExperimentalKeepAnnotations(true);
-      builder.getBuilder().setEnableExperimentalVersionedKeepEdgeAnnotations(false);
-      return this;
+      this.useEdgeExtraction = useEdgeExtraction;
+      if (useEdgeExtraction) {
+        builder.getBuilder().setEnableExperimentalKeepAnnotations(false);
+        builder.getBuilder().setEnableExperimentalVersionedKeepEdgeAnnotations(true);
+      } else {
+        builder.getBuilder().setEnableExperimentalKeepAnnotations(true);
+        builder.getBuilder().setEnableExperimentalVersionedKeepEdgeAnnotations(false);
+      }
     }
 
     @Override
@@ -224,7 +220,7 @@
       return this;
     }
 
-    private void extractAndAdd(byte[] classFileData) throws IOException {
+    private void extractAndAdd(byte[] classFileData) {
       builder.addProgramClassFileData(classFileData);
       if (useEdgeExtraction) {
         List<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(classFileData);
@@ -241,19 +237,21 @@
               null,
               "java/lang/Object",
               null);
-          for (KeepDeclaration decl : declarations) {
-            if (!decl.isKeepEdge()) {
-              throw new Unimplemented("Support check declarations...");
-            } else {
-              KeepEdge edge = decl.asKeepEdge();
-              KeepEdgeWriter.writeExtractedEdge(
-                  edge,
-                  (descriptor, visible) ->
-                      KeepAnnoTestUtils.wrap(classWriter.visitAnnotation(descriptor, visible)));
-            }
-          }
+          KeepEdgeWriter.writeExtractedEdges(
+              declarations,
+              (descriptor, visible) ->
+                  KeepAnnoTestUtils.wrap(classWriter.visitAnnotation(descriptor, visible)));
           classWriter.visitEnd();
-          builder.addProgramClassFileData(classWriter.toByteArray());
+          builder
+              .getBuilder()
+              .addClassProgramData(
+                  classWriter.toByteArray(),
+                  new Origin(Origin.root()) {
+                    @Override
+                    public String part() {
+                      return "edge-extraction";
+                    }
+                  });
         }
       }
     }
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 5e54002..a9dd273 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
@@ -8,6 +8,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
+import com.android.tools.r8.keepanno.annotations.InstanceOfPattern;
 import com.android.tools.r8.keepanno.annotations.KeepTarget;
 import com.android.tools.r8.keepanno.annotations.UsesReflection;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -41,7 +43,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
@@ -92,7 +93,10 @@
 
     @UsesReflection({
       @KeepTarget(
-          instanceOfClassConstantExclusive = Base.class,
+          instanceOfPattern =
+              @InstanceOfPattern(
+                  inclusive = false,
+                  classNamePattern = @ClassNamePattern(constant = Base.class)),
           constraints = {},
           constrainAnnotations = @AnnotationPattern(constant = Anno.class))
     })
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 02136bb..464ee91 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepBindingTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepBindingTest.java
@@ -37,7 +37,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepClassRules(A.class, B.class)
         .addKeepMainRule(TestClass.class)
@@ -50,7 +49,6 @@
   @Test
   public void testNoKeepOnClass() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
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 4abd731..c1e5683 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEmptyClassTest.java
@@ -37,7 +37,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addProgramClassFileData(
             transformer(B.class).removeMethods(MethodPredicate.all()).transform())
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java
index 20f8858..9ac84d1 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java
@@ -58,7 +58,6 @@
     assertTrue(parameters.isShrinker());
     Box<Path> lib = new Box<>();
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getLibraryClasses())
         .setExcludedOuterClass(getClass())
         .applyIfShrinker(b -> lib.set(b.compile().inspect(this::checkLibraryOutput).writeToZip()));
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 121b904..0a2e16b 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java
@@ -39,7 +39,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInclusiveInstanceOfTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInclusiveInstanceOfTest.java
index 895f7e7..85809df 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepInclusiveInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInclusiveInstanceOfTest.java
@@ -30,7 +30,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java
index fe9b59c..c6d3b7d 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java
@@ -58,7 +58,6 @@
     assertTrue(parameters.isShrinker());
     Box<Path> lib = new Box<>();
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getLibraryClasses())
         .setExcludedOuterClass(getClass())
         .applyIfShrinker(b -> lib.set(b.compile().inspect(this::checkLibraryOutput).writeToZip()));
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepMethodsAccessFlagsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepMethodsAccessFlagsTest.java
index d560087..0de2eaf 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepMethodsAccessFlagsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepMethodsAccessFlagsTest.java
@@ -41,7 +41,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepMethodsEmptyAccessFlagsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepMethodsEmptyAccessFlagsTest.java
index 7ebe37e..cac4ddb 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepMethodsEmptyAccessFlagsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepMethodsEmptyAccessFlagsTest.java
@@ -40,7 +40,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepNameAndInstanceOfTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepNameAndInstanceOfTest.java
index b59ee95..b36cca3 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepNameAndInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepNameAndInstanceOfTest.java
@@ -31,7 +31,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
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 f7f57ce..f1fe6f5 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
@@ -38,11 +38,10 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
-        // The "all members" target will create an unused "all fields" rule.
+        // The extraction based "all members" target will create an unused "all fields" rule.
         .allowUnusedProguardConfigurationRules()
         .run(TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepStaticBindingTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepStaticBindingTest.java
index 21260a5..1fbe385 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepStaticBindingTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepStaticBindingTest.java
@@ -36,7 +36,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .setExcludedOuterClass(getClass())
         .run(TestClass.class)
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 e3a9569..9afb8b1 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
@@ -36,7 +36,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java
index 79a1720..dfe0e2b 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java
@@ -40,7 +40,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
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 988e441..1c0313a 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
@@ -35,7 +35,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
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 481cc35..92f2515 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
@@ -35,7 +35,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
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 544fcad..e1c5dd9 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
@@ -38,7 +38,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .addKeepMainRule(TestClass.class)
         .setExcludedOuterClass(getClass())
diff --git a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
index 820b78b..52850da 100644
--- a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
@@ -47,7 +47,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .setExcludedOuterClass(getClass())
         .run(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/keepanno/MethodNameStringPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/MethodNameStringPatternsTest.java
index bfb85ad..9306ca4 100644
--- a/src/test/java/com/android/tools/r8/keepanno/MethodNameStringPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/MethodNameStringPatternsTest.java
@@ -40,7 +40,6 @@
   @Test
   public void test() throws Exception {
     testForKeepAnno(parameters)
-        .skipEdgeExtraction()
         .addProgramClasses(getInputClasses())
         .setExcludedOuterClass(getClass())
         .run(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index 977afd5..325960e 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -73,6 +73,7 @@
   private static final ClassReference STRING_PATTERN = annoClass("StringPattern");
   private static final ClassReference TYPE_PATTERN = annoClass("TypePattern");
   private static final ClassReference CLASS_NAME_PATTERN = annoClass("ClassNamePattern");
+  private static final ClassReference INSTANCE_OF_PATTERN = annoClass("InstanceOfPattern");
   private static final ClassReference ANNOTATION_PATTERN = annoClass("AnnotationPattern");
   private static final ClassReference USES_REFLECTION = annoClass("UsesReflection");
   private static final ClassReference USED_BY_REFLECTION = annoClass("UsedByReflection");
@@ -81,6 +82,8 @@
   private static final ClassReference CHECK_OPTIMIZED_OUT = annoClass("CheckOptimizedOut");
   private static final ClassReference EXTRACTED_KEEP_ANNOTATIONS =
       annoClass("ExtractedKeepAnnotations");
+  private static final ClassReference EXTRACTED_KEEP_ANNOTATION =
+      annoClass("ExtractedKeepAnnotation");
   private static final ClassReference KEEP_EDGE = annoClass("KeepEdge");
   private static final ClassReference KEEP_BINDING = annoClass("KeepBinding");
   private static final ClassReference KEEP_TARGET = annoClass("KeepTarget");
@@ -200,6 +203,8 @@
       "@" + simpleName(TYPE_PATTERN) + "(name = \"\")";
   private static final String DEFAULT_INVALID_CLASS_NAME_PATTERN =
       "@" + simpleName(CLASS_NAME_PATTERN) + "(simpleName = \"\")";
+  private static final String DEFAULT_ANY_INSTANCE_OF_PATTERN =
+      "@" + simpleName(INSTANCE_OF_PATTERN) + "()";
 
   private static ClassReference astClass(String simpleName) {
     return classFromTypeName(AST_PKG + simpleName);
@@ -277,6 +282,11 @@
       return requiredValue(JAVA_STRING);
     }
 
+    public GroupMember defaultBooleanValue(boolean value) {
+      setType("boolean");
+      return setValue(value ? "true" : "false");
+    }
+
     public GroupMember defaultValue(ClassReference type, String value) {
       setType(simpleName(type));
       return setValue(value);
@@ -591,6 +601,44 @@
       // boolean anyReference() default false;
     }
 
+    private Group instanceOfPatternInclusive() {
+      return new Group("instance-of-inclusive")
+          .addMember(
+              new GroupMember("inclusive")
+                  .setDocTitle("True if the pattern should include the directly matched classes.")
+                  .addParagraph(
+                      "If false, the pattern is exclusive and only matches classes that are",
+                      "strict subclasses of the pattern.")
+                  .defaultBooleanValue(true));
+    }
+
+    private Group instanceOfPatternClassNamePattern() {
+      return new Group("instance-of-class-name-pattern")
+          .addMember(
+              new GroupMember("classNamePattern")
+                  .setDocTitle("Instances of classes matching the class-name pattern.")
+                  .defaultValue(CLASS_NAME_PATTERN, DEFAULT_INVALID_CLASS_NAME_PATTERN));
+    }
+
+    private Group classNamePatternFullNameGroup() {
+      return new Group(CLASS_NAME_GROUP)
+          .allowMutuallyExclusiveWithOtherGroups()
+          .addMember(
+              new GroupMember("name")
+                  .setDocTitle(
+                      "Define the " + CLASS_NAME_GROUP + " pattern by fully qualified class name.")
+                  .setDocReturn("The qualified class name that defines the class.")
+                  .defaultEmptyString())
+          .addMember(
+              new GroupMember("constant")
+                  .setDocTitle(
+                      "Define the "
+                          + CLASS_NAME_GROUP
+                          + " pattern by reference to a Class constant.")
+                  .setDocReturn("The class-constant that defines the class.")
+                  .defaultObjectClass());
+    }
+
     private Group classNamePatternSimpleNameGroup() {
       return new Group("class-simple-name")
           .addMember(
@@ -848,12 +896,20 @@
           .defaultObjectClass();
     }
 
+    private GroupMember instanceOfPattern() {
+      return new GroupMember("instanceOfPattern")
+          .setDocTitle("Define the " + INSTANCE_OF_GROUP + " with a pattern.")
+          .setDocReturn("The pattern that defines what instance-of the class must be.")
+          .defaultValue(INSTANCE_OF_PATTERN, DEFAULT_ANY_INSTANCE_OF_PATTERN);
+    }
+
     private Group createClassInstanceOfPatternGroup() {
       return new Group(INSTANCE_OF_GROUP)
           .addMember(instanceOfClassName())
           .addMember(instanceOfClassNameExclusive())
           .addMember(instanceOfClassConstant())
           .addMember(instanceOfClassConstantExclusive())
+          .addMember(instanceOfPattern())
           .addDocFooterParagraph(
               "If none are specified the default is to match any class instance.");
     }
@@ -1235,9 +1291,38 @@
       println();
       withIndent(
           () -> {
-            classNamePatternSimpleNameGroup().generate(this);
+            Group exactNameGroup = classNamePatternFullNameGroup();
+            Group simpleNameGroup = classNamePatternSimpleNameGroup();
+            Group packageGroup = classNamePatternPackageGroup();
+            exactNameGroup.addMutuallyExclusiveGroups(simpleNameGroup, packageGroup);
+
+            exactNameGroup.generate(this);
             println();
-            classNamePatternPackageGroup().generate(this);
+            simpleNameGroup.generate(this);
+            println();
+            packageGroup.generate(this);
+          });
+      println();
+      println("}");
+    }
+
+    private void generateInstanceOfPattern() {
+      printCopyRight(2024);
+      printPackage("annotations");
+      printImports(ANNOTATION_IMPORTS);
+      DocPrinter.printer()
+          .setDocTitle("A pattern structure for matching instances of classes and interfaces.")
+          .addParagraph("If no properties are set, the default pattern matches any instance.")
+          .printDoc(this::println);
+      println("@Target(ElementType.ANNOTATION_TYPE)");
+      println("@Retention(RetentionPolicy.CLASS)");
+      println("public @interface " + simpleName(INSTANCE_OF_PATTERN) + " {");
+      println();
+      withIndent(
+          () -> {
+            instanceOfPatternInclusive().generate(this);
+            println();
+            instanceOfPatternClassNamePattern().generate(this);
           });
       println();
       println("}");
@@ -1608,6 +1693,7 @@
             generateStringPatternConstants();
             generateTypePatternConstants();
             generateClassNamePatternConstants();
+            generateInstanceOfPatternConstants();
             generateAnnotationPatternConstants();
           });
       println("}");
@@ -1619,10 +1705,21 @@
     }
 
     private void generateExtractedKeepAnnotationsConstants() {
-      println("public static final class Extracted {");
+      println("public static final class ExtractedAnnotations {");
       withIndent(
           () -> {
             generateAnnotationConstants(EXTRACTED_KEEP_ANNOTATIONS);
+            new GroupMember("value")
+                .setDocTitle("Extracted normalized keep edges.")
+                .requiredArrayValue(KEEP_EDGE)
+                .generateConstants(this);
+          });
+      println("}");
+      println();
+      println("public static final class ExtractedAnnotation {");
+      withIndent(
+          () -> {
+            generateAnnotationConstants(EXTRACTED_KEEP_ANNOTATION);
             new GroupMember("version")
                 .setDocTitle("Extraction version used to generate this keep annotation.")
                 .requiredStringValue()
@@ -1631,9 +1728,19 @@
                 .setDocTitle("Extraction context from which this keep annotation is generated.")
                 .requiredStringValue()
                 .generateConstants(this);
-            new GroupMember("edges")
-                .setDocTitle("Extracted normalized keep edges.")
-                .requiredArrayValue(KEEP_EDGE)
+            new Group("keep-annotation")
+                .addMember(
+                    new GroupMember("edge")
+                        .setDocTitle("Extracted normalized keep edge.")
+                        .requiredValue(KEEP_EDGE))
+                .addMember(
+                    new GroupMember("checkRemoved")
+                        .setDocTitle("Extracted check removed.")
+                        .defaultBooleanValue(false))
+                .addMember(
+                    new GroupMember("checkOptimizedOut")
+                        .setDocTitle("Extracted check optimized out.")
+                        .defaultBooleanValue(false))
                 .generateConstants(this);
           });
       println("}");
@@ -1919,6 +2026,7 @@
       withIndent(
           () -> {
             generateAnnotationConstants(CLASS_NAME_PATTERN);
+            classNamePatternFullNameGroup().generateConstants(this);
             classNamePatternSimpleNameGroup().generateConstants(this);
             classNamePatternPackageGroup().generateConstants(this);
           });
@@ -1926,6 +2034,18 @@
       println();
     }
 
+    private void generateInstanceOfPatternConstants() {
+      println("public static final class InstanceOfPattern {");
+      withIndent(
+          () -> {
+            generateAnnotationConstants(INSTANCE_OF_PATTERN);
+            instanceOfPatternInclusive().generateConstants(this);
+            instanceOfPatternClassNamePattern().generateConstants(this);
+          });
+      println("}");
+      println();
+    }
+
     private void generateAnnotationPatternConstants() {
       println("public static final class AnnotationPattern {");
       withIndent(
@@ -1967,6 +2087,7 @@
       writeFile(source(STRING_PATTERN), Generator::generateStringPattern, write);
       writeFile(source(TYPE_PATTERN), Generator::generateTypePattern, write);
       writeFile(source(CLASS_NAME_PATTERN), Generator::generateClassNamePattern, write);
+      writeFile(source(INSTANCE_OF_PATTERN), Generator::generateInstanceOfPattern, write);
       writeFile(source(ANNOTATION_PATTERN), Generator::generateAnnotationPattern, write);
       writeFile(source(KEEP_BINDING), Generator::generateKeepBinding, write);
       writeFile(source(KEEP_TARGET), Generator::generateKeepTarget, write);
diff --git a/third_party/dependencies.tar.gz.sha1 b/third_party/dependencies.tar.gz.sha1
index e6e6581..a916904 100644
--- a/third_party/dependencies.tar.gz.sha1
+++ b/third_party/dependencies.tar.gz.sha1
@@ -1 +1 @@
-799dc154db4bd7687d0f22b90590555da831ef95
\ No newline at end of file
+b359a23d49694aabe18bf8c47b2bf3e5b1b77322
\ No newline at end of file
diff --git a/third_party/dependencies_plugin.tar.gz.sha1 b/third_party/dependencies_plugin.tar.gz.sha1
index f85351e..d6ec64f 100644
--- a/third_party/dependencies_plugin.tar.gz.sha1
+++ b/third_party/dependencies_plugin.tar.gz.sha1
@@ -1 +1 @@
-a58a2edb72ff3a3126e4846057dd55eb0e7c27f8
\ No newline at end of file
+ce80383f6ea9554ad1bc99e9be4b5c040a7f7e09
\ No newline at end of file
diff --git a/third_party/gradle.tar.gz.sha1 b/third_party/gradle.tar.gz.sha1
new file mode 100644
index 0000000..c411ad6
--- /dev/null
+++ b/third_party/gradle.tar.gz.sha1
@@ -0,0 +1 @@
+2316517b7942b638f56f61568f10f364308b0600
\ No newline at end of file
diff --git a/third_party/gradle/gradle-8.0.tar.gz.sha1 b/third_party/gradle/gradle-8.0.tar.gz.sha1
deleted file mode 100644
index d67ee97..0000000
--- a/third_party/gradle/gradle-8.0.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-dd1f4697d211954afb3034214453c2e06769a036
\ No newline at end of file
diff --git a/third_party/gradle/gradle-8.3.tar.gz.sha1 b/third_party/gradle/gradle-8.3.tar.gz.sha1
deleted file mode 100644
index 4afa179..0000000
--- a/third_party/gradle/gradle-8.3.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-214033edc4cfc5f29f0d3ea437f8557f424a6577
\ No newline at end of file
diff --git a/third_party/gradle/gradle.tar.gz.sha1 b/third_party/gradle/gradle.tar.gz.sha1
deleted file mode 100644
index cbf0dd4..0000000
--- a/third_party/gradle/gradle.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-6b27c3955e734eafafaa3d4e35c854a57c48b427
\ No newline at end of file
diff --git a/tools/archive_smali.py b/tools/archive_smali.py
index e19da70..0c877e5 100755
--- a/tools/archive_smali.py
+++ b/tools/archive_smali.py
@@ -63,7 +63,7 @@
     if utils.is_bot() and not utils.IsWindows():
         set_rlimit_to_max()
 
-    utils.DownloadFromGoogleCloudStorage(utils.JAVA11_SHA_FILE)
+    utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
     with utils.TempDir() as temp:
         # Resolve dry run location to support relative directories.
         dry_run_output = None
@@ -101,11 +101,11 @@
 
             print('Building version: %s' % version)
 
-            # Build release to local Maven repository compiling with JDK-11.
+            # Build release to local Maven repository compiling with JDK-8.
             m2 = os.path.join(temp, 'm2')
             os.mkdir(m2)
             env = os.environ.copy()
-            env["JAVA_HOME"] = jdk.GetJdk11Home()
+            env["JAVA_HOME"] = jdk.GetJdk8Home()
             subprocess.check_call([
                 './gradlew',
                 '-Dmaven.repo.local=%s' % m2, 'release', 'test',
diff --git a/tools/create_local_maven_with_dependencies.py b/tools/create_local_maven_with_dependencies.py
index 36f9d1f..c151352 100755
--- a/tools/create_local_maven_with_dependencies.py
+++ b/tools/create_local_maven_with_dependencies.py
@@ -24,8 +24,8 @@
 ASM_VERSION = '9.6' # When updating update tools/asmifier.py and Toolhelper as well.
 ESPRESSO_VERSION = '3.0.0'
 FASTUTIL_VERSION = '7.2.1'
-KOTLIN_METADATA_VERSION = '0.9.0'
-KOTLIN_VERSION = '1.9.0'
+KOTLIN_METADATA_VERSION = '0.7.0'
+KOTLIN_VERSION = '1.9.20'
 GUAVA_VERSION = '32.1.2-jre'
 GSON_VERSION = '2.10.1'
 JAVASSIST_VERSION = '3.29.2-GA'
@@ -97,10 +97,10 @@
 ]
 
 PLUGIN_DEPENDENCIES = [
-  'org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:4.1.0',
+  'org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:pom:4.2.1',
   'org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.9.10',
-  'net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:3.0.1',
-  'org.spdx.sbom:org.spdx.sbom.gradle.plugin:0.4.0',
+  'net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:pom:3.0.1',
+  'org.spdx.sbom:org.spdx.sbom.gradle.plugin:pom:0.4.0',
   # See https://github.com/FasterXML/jackson-core/issues/999.
   'ch.randelshofer:fastdoubleparser:0.8.0',
 ]
diff --git a/tools/gradle.py b/tools/gradle.py
index 76039f9..ef77a5c 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -15,17 +15,16 @@
 import jdk
 import utils
 
-GRADLE_DIR = os.path.join(utils.REPO_ROOT, 'third_party', 'gradle')
-GRADLE8_SHA1 = os.path.join(GRADLE_DIR, 'gradle-8.3.tar.gz.sha1')
-GRADLE8_TGZ = os.path.join(GRADLE_DIR, 'gradle-8.3.tar.gz')
+GRADLE8_SHA1 = os.path.join(utils.THIRD_PARTY, 'gradle.tar.gz.sha1')
+GRADLE8_TGZ = os.path.join(utils.THIRD_PARTY, 'gradle.tar.gz')
 
 
 def get_gradle():
-    gradle_dir = 'gradle-8.3'
+    gradle_dir = os.path.join(utils.THIRD_PARTY, 'gradle')
     if utils.IsWindows():
-        return os.path.join(GRADLE_DIR, gradle_dir, 'bin', 'gradle.bat')
+        return os.path.join(gradle_dir, 'bin', 'gradle.bat')
     else:
-        return os.path.join(GRADLE_DIR, gradle_dir, 'bin', 'gradle')
+        return os.path.join(gradle_dir, 'bin', 'gradle')
 
 
 def ParseOptions():