Merge commit '60fa9be75820ce92b13f1ceb511f63c18800f134' into dev-release

Change-Id: I4ba237431b3f627e7581909c9f0031d2fbe28950
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index e8f153a..5dfc0a5 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -55,7 +55,7 @@
   JDK_11("jdk-11", 11),
   JDK_17("jdk-17", 17),
   JDK_21("jdk-21", 21),
-  JDK_24("jdk-24", 24);
+  JDK_25("jdk-25", 25);
 
   fun isJdk8(): Boolean {
     return this == JDK_8
diff --git a/d8_r8/test/build.gradle.kts b/d8_r8/test/build.gradle.kts
index 4bda2f7..729e2d7 100644
--- a/d8_r8/test/build.gradle.kts
+++ b/d8_r8/test/build.gradle.kts
@@ -64,7 +64,7 @@
     dependsOn(gradle.includedBuild("tests_java_11").task(":clean"))
     dependsOn(gradle.includedBuild("tests_java_17").task(":clean"))
     dependsOn(gradle.includedBuild("tests_java_21").task(":clean"))
-    dependsOn(gradle.includedBuild("tests_java_24").task(":clean"))
+    dependsOn(gradle.includedBuild("tests_java_25").task(":clean"))
   }
 
   val packageTests by registering(Jar::class) {
diff --git a/d8_r8/test/settings.gradle.kts b/d8_r8/test/settings.gradle.kts
index 73ef7f8..71cd9e5 100644
--- a/d8_r8/test/settings.gradle.kts
+++ b/d8_r8/test/settings.gradle.kts
@@ -36,4 +36,4 @@
 includeBuild(root.resolve("test_modules").resolve("tests_java_11"))
 includeBuild(root.resolve("test_modules").resolve("tests_java_17"))
 includeBuild(root.resolve("test_modules").resolve("tests_java_21"))
-includeBuild(root.resolve("test_modules").resolve("tests_java_24"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_25"))
diff --git a/d8_r8/test_modules/tests_java_24/build.gradle.kts b/d8_r8/test_modules/tests_java_25/build.gradle.kts
similarity index 85%
rename from d8_r8/test_modules/tests_java_24/build.gradle.kts
rename to d8_r8/test_modules/tests_java_25/build.gradle.kts
index 2492e3b..c0f8a64 100644
--- a/d8_r8/test_modules/tests_java_24/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_25/build.gradle.kts
@@ -14,10 +14,10 @@
 
 java {
   sourceSets.test.configure {
-    java.srcDir(root.resolveAll("src", "test", "java24"))
+    java.srcDir(root.resolveAll("src", "test", "java25"))
   }
-  sourceCompatibility = JavaVersion.VERSION_24
-  targetCompatibility = JavaVersion.VERSION_24
+  sourceCompatibility = JavaVersion.VERSION_25
+  targetCompatibility = JavaVersion.VERSION_25
 }
 
 val testbaseJavaCompileTask = projectTask("testbase", "compileJava")
@@ -36,14 +36,14 @@
     dependsOn(gradle.includedBuild("shared").task(":downloadDeps"))
     options.setFork(true)
     options.forkOptions.memoryMaximumSize = "3g"
-    options.forkOptions.executable = getCompilerPath(Jdk.JDK_24)
+    options.forkOptions.executable = getCompilerPath(Jdk.JDK_25)
   }
 
   withType<Test> {
     notCompatibleWithConfigurationCache(
       "Failure storing the configuration cache: cannot serialize object of type 'org.gradle.api.internal.project.DefaultProject', a subtype of 'org.gradle.api.Project', as these are not supported with the configuration cache")
     TestingState.setUpTestingState(this)
-    javaLauncher = getJavaLauncher(Jdk.JDK_24)
+    javaLauncher = getJavaLauncher(Jdk.JDK_25)
     systemProperty("TEST_DATA_LOCATION",
                    layout.buildDirectory.dir("classes/java/test").get().toString())
     systemProperty("TESTBASE_DATA_LOCATION",
diff --git a/d8_r8/test_modules/tests_java_24/settings.gradle.kts b/d8_r8/test_modules/tests_java_25/settings.gradle.kts
similarity index 95%
rename from d8_r8/test_modules/tests_java_24/settings.gradle.kts
rename to d8_r8/test_modules/tests_java_25/settings.gradle.kts
index c8111c1..0f47405 100644
--- a/d8_r8/test_modules/tests_java_24/settings.gradle.kts
+++ b/d8_r8/test_modules/tests_java_25/settings.gradle.kts
@@ -21,7 +21,7 @@
   }
 }
 
-rootProject.name = "tests_java_24"
+rootProject.name = "tests_java_25"
 val root = rootProject.projectDir.parentFile.parentFile
 
 includeBuild(root.resolve("shared"))
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 3a60dff..76170b1 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -1551,7 +1551,7 @@
       }
     }
     builders {
-      name: "linux-jdk24"
+      name: "linux-jdk25"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cpu:x86-64"
@@ -1568,7 +1568,7 @@
         '  "builder_group": "internal.client.r8",'
         '  "recipe": "rex",'
         '  "test_options": ['
-        '    "--runtimes=jdk24",'
+        '    "--runtimes=jdk25",'
         '    "--command_cache_dir=/tmp/ccache",'
         '    "--tool=r8",'
         '    "--print-times",'
@@ -1588,7 +1588,7 @@
       }
     }
     builders {
-      name: "linux-jdk24_release"
+      name: "linux-jdk25_release"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cpu:x86-64"
@@ -1605,7 +1605,7 @@
         '  "builder_group": "internal.client.r8",'
         '  "recipe": "rex",'
         '  "test_options": ['
-        '    "--runtimes=jdk24",'
+        '    "--runtimes=jdk25",'
         '    "--command_cache_dir=/tmp/ccache",'
         '    "--tool=r8",'
         '    "--print-times",'
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg
index 7ee27d7..e6c8157 100644
--- a/infra/config/global/generated/luci-milo.cfg
+++ b/infra/config/global/generated/luci-milo.cfg
@@ -51,9 +51,9 @@
     short_name: "jdk21"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk24"
+    name: "buildbucket/luci.r8.ci/linux-jdk25"
     category: "R8"
-    short_name: "jdk24"
+    short_name: "jdk25"
   }
   builders {
     name: "buildbucket/luci.r8.ci/linux-android-4.0"
@@ -206,9 +206,9 @@
     short_name: "jdk21"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk24_release"
+    name: "buildbucket/luci.r8.ci/linux-jdk25_release"
     category: "Release|R8"
-    short_name: "jdk24"
+    short_name: "jdk25"
   }
   builders {
     name: "buildbucket/luci.r8.ci/linux-android-4.0_release"
diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg
index f072a67..a13297c 100644
--- a/infra/config/global/generated/luci-notify.cfg
+++ b/infra/config/global/generated/luci-notify.cfg
@@ -480,7 +480,7 @@
   }
   builders {
     bucket: "ci"
-    name: "linux-jdk24"
+    name: "linux-jdk25"
     repository: "https://r8.googlesource.com/r8"
   }
 }
@@ -492,7 +492,7 @@
   }
   builders {
     bucket: "ci"
-    name: "linux-jdk24_release"
+    name: "linux-jdk25_release"
     repository: "https://r8.googlesource.com/r8"
   }
 }
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index 04e6f0f..ef9103f 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -617,7 +617,7 @@
   }
 }
 job {
-  id: "linux-jdk24"
+  id: "linux-jdk25"
   realm: "ci"
   acl_sets: "ci"
   triggering_policy {
@@ -627,11 +627,11 @@
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "ci"
-    builder: "linux-jdk24"
+    builder: "linux-jdk25"
   }
 }
 job {
-  id: "linux-jdk24_release"
+  id: "linux-jdk25_release"
   realm: "ci"
   acl_sets: "ci"
   triggering_policy {
@@ -642,7 +642,7 @@
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "ci"
-    builder: "linux-jdk24_release"
+    builder: "linux-jdk25_release"
   }
 }
 job {
@@ -821,17 +821,6 @@
   }
 }
 trigger {
-  id: "branch-gitiles-8.11-forward"
-  realm: "ci"
-  acl_sets: "ci"
-  triggers: "linux-jdk24_release"
-  gitiles {
-    repo: "https://r8.googlesource.com/r8"
-    refs: "regexp:refs/heads/([8]\\.[1-9][1-9]|[9]\\.[0-9]+)"
-    path_regexps: "src/main/java/com/android/tools/r8/Version.java"
-  }
-}
-trigger {
   id: "branch-gitiles-8.5-forward"
   realm: "ci"
   acl_sets: "ci"
@@ -854,6 +843,17 @@
   }
 }
 trigger {
+  id: "branch-gitiles-9.0-forward"
+  realm: "ci"
+  acl_sets: "ci"
+  triggers: "linux-jdk25_release"
+  gitiles {
+    repo: "https://r8.googlesource.com/r8"
+    refs: "regexp:refs/heads/([9]\\.[0-9]+)"
+    path_regexps: "src/main/java/com/android/tools/r8/Version.java"
+  }
+}
+trigger {
   id: "branch-gitiles-trigger"
   realm: "ci"
   acl_sets: "ci"
@@ -909,7 +909,7 @@
   triggers: "linux-jdk11"
   triggers: "linux-jdk17"
   triggers: "linux-jdk21"
-  triggers: "linux-jdk24"
+  triggers: "linux-jdk25"
   triggers: "linux-jdk8"
   triggers: "linux-none"
   triggers: "linux-old"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 49592dc..5760b29 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -115,10 +115,10 @@
 )
 
 luci.gitiles_poller(
-    name = "branch-gitiles-8.11-forward",
+    name = "branch-gitiles-9.0-forward",
     bucket = "ci",
     repo = "https://r8.googlesource.com/r8",
-    refs = ["refs/heads/([8]\\.[1-9][1-9]|[9]\\.[0-9]+)"],
+    refs = ["refs/heads/([9]\\.[0-9]+)"],
     path_regexps = ["src/main/java/com/android/tools/r8/Version.java"],
 )
 
@@ -386,9 +386,9 @@
     ["--runtimes=jdk21", "--command_cache_dir=/tmp/ccache"],
 )
 r8_tester_with_default(
-    "linux-jdk24",
-    ["--runtimes=jdk24", "--command_cache_dir=/tmp/ccache"],
-    release_trigger = ["branch-gitiles-8.11-forward"],
+    "linux-jdk25",
+    ["--runtimes=jdk25", "--command_cache_dir=/tmp/ccache"],
+    release_trigger = ["branch-gitiles-9.0-forward"],
 )
 
 r8_tester_with_default(
diff --git a/scripts/add-openjdk.sh b/scripts/add-openjdk.sh
index 0eecb48..9068086 100755
--- a/scripts/add-openjdk.sh
+++ b/scripts/add-openjdk.sh
@@ -18,7 +18,7 @@
 
 # Now run script with fingers crossed!
 
-JDK_VERSION="24"
+JDK_VERSION="25"
 JDK_VERSION_FULL=${JDK_VERSION}
 # For ea versions the full version name has a postfix.
 # JDK_VERSION_FULL="${JDK_VERSION}-ea+33"
@@ -62,4 +62,4 @@
 
 git add *.sha1
 
-echo "Update additional files, see https://r8-review.googlesource.com/c/r8/+/61909"
+echo "Update additional files, see https://r8-review.googlesource.com/c/r8/+/111040"
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 43953a0..91230f3 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1082,10 +1082,6 @@
 
     }
 
-    void setEnableExperimentalCheckEnumUnboxed() {
-      parserOptionsBuilder.setEnableExperimentalCheckEnumUnboxed(true);
-    }
-
     // Internal for-testing method to allow proguard options only available for testing.
     void setEnableTestProguardOptions() {
       parserOptionsBuilder.setEnableTestingOptions(true);
diff --git a/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ClassFlagEvent.java b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ClassFlagEvent.java
new file mode 100644
index 0000000..b6bd9cf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ClassFlagEvent.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2025, 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.assistant.postprocessing.model;
+
+import com.android.tools.r8.assistant.runtime.ReflectiveEventType;
+import com.android.tools.r8.assistant.runtime.ReflectiveOperationReceiver.ClassFlag;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.KeepInfoCollectionExported;
+
+public class ClassFlagEvent extends ReflectiveEvent {
+  private final DexType holder;
+  private final ClassFlag classFlag;
+
+  protected ClassFlagEvent(
+      ReflectiveEventType eventType, String[] stack, String[] args, DexItemFactory factory) {
+    super(eventType, stack);
+    assert args.length == 2;
+    holder = toType(args[0], factory);
+    classFlag = ClassFlag.valueOf(args[1]);
+  }
+
+  public ClassFlag getClassFlag() {
+    return classFlag;
+  }
+
+  @Override
+  public boolean isClassFlagEvent() {
+    return true;
+  }
+
+  @Override
+  public ClassFlagEvent asClassFlagEvent() {
+    return this;
+  }
+
+  @Override
+  public String getContentsString() {
+    return holder.toString() + "#" + classFlag;
+  }
+
+  @Override
+  public boolean isKeptBy(KeepInfoCollectionExported keepInfoCollectionExported) {
+    // TODO(b/428836085): Check inner properties of the keep rules, holder, type and name may have
+    //  to be preserved.
+    return keepInfoCollectionExported.getKeepClassInfo(holder.asTypeReference()) != null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ClassNewInstance.java b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ClassNewInstance.java
new file mode 100644
index 0000000..dfdab8d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ClassNewInstance.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2025, 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.assistant.postprocessing.model;
+
+import com.android.tools.r8.assistant.runtime.ReflectiveEventType;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.KeepInfoCollectionExported;
+
+public class ClassNewInstance extends ReflectiveEvent {
+
+  private final DexType type;
+
+  protected ClassNewInstance(
+      ReflectiveEventType eventType, String[] stack, String[] args, DexItemFactory factory) {
+    super(eventType, stack);
+    type = toType(args[0], factory);
+  }
+
+  public DexType getType() {
+    return type;
+  }
+
+  @Override
+  public boolean isClassNewInstance() {
+    return true;
+  }
+
+  @Override
+  public ClassNewInstance asClassNewInstance() {
+    return this;
+  }
+
+  @Override
+  public String getContentsString() {
+    return type.toSourceString();
+  }
+
+  @Override
+  public boolean isKeptBy(KeepInfoCollectionExported keepInfoCollectionExported) {
+    // TODO(b/428836085): Check inner properties of the keep rules, holder, type and name may have
+    //  to be preserved.
+    return keepInfoCollectionExported.getKeepClassInfo(type.asTypeReference()) != null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ProxyNewProxyInstance.java b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ProxyNewProxyInstance.java
new file mode 100644
index 0000000..248a0fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ProxyNewProxyInstance.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2025, 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.assistant.postprocessing.model;
+
+import com.android.tools.r8.assistant.runtime.ReflectiveEventType;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.KeepInfoCollectionExported;
+import com.android.tools.r8.utils.ArrayUtils;
+import java.util.Arrays;
+
+public class ProxyNewProxyInstance extends ReflectiveEvent {
+
+  private final DexType classLoader;
+  private final DexType[] interfaces;
+  private final String invocationHandler;
+
+  public ProxyNewProxyInstance(
+      ReflectiveEventType eventType, String[] stack, String[] args, DexItemFactory factory) {
+    super(eventType, stack);
+    assert args.length > 2;
+    classLoader = toTypeOrTripleStar(args[0], factory);
+    invocationHandler = args[1];
+    interfaces = new DexType[args.length - 2];
+    for (int i = 2; i < args.length; i++) {
+      interfaces[i - 2] = toType(args[i], factory);
+    }
+  }
+
+  public DexType[] getInterfaces() {
+    return interfaces;
+  }
+
+  @Override
+  public boolean isProxyNewProxyInstance() {
+    return true;
+  }
+
+  @Override
+  public ProxyNewProxyInstance asProxyNewProxyInstance() {
+    return this;
+  }
+
+  @Override
+  public String getContentsString() {
+    return invocationHandler + ", " + classLoader + ", " + Arrays.toString(interfaces);
+  }
+
+  @Override
+  public boolean isKeptBy(KeepInfoCollectionExported keepInfoCollectionExported) {
+    return ArrayUtils.none(
+        interfaces,
+        itf -> keepInfoCollectionExported.getKeepClassInfo(itf.asTypeReference()) == null);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ReflectiveEvent.java b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ReflectiveEvent.java
index 26090d0..cfa48ac 100644
--- a/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ReflectiveEvent.java
+++ b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ReflectiveEvent.java
@@ -60,6 +60,14 @@
     return null;
   }
 
+  public boolean isProxyNewProxyInstance() {
+    return false;
+  }
+
+  public ProxyNewProxyInstance asProxyNewProxyInstance() {
+    return null;
+  }
+
   public boolean isClassGetMember() {
     return false;
   }
@@ -68,6 +76,22 @@
     return null;
   }
 
+  public boolean isClassNewInstance() {
+    return false;
+  }
+
+  public ClassNewInstance asClassNewInstance() {
+    return null;
+  }
+
+  public boolean isClassFlagEvent() {
+    return false;
+  }
+
+  public ClassFlagEvent asClassFlagEvent() {
+    return null;
+  }
+
   public boolean isClassGetMembers() {
     return false;
   }
@@ -89,7 +113,7 @@
       ReflectiveEventType eventType, String[] stack, String[] args, DexItemFactory factory) {
     switch (eventType) {
       case CLASS_NEW_INSTANCE:
-        break;
+        return new ClassNewInstance(eventType, stack, args, factory);
       case CLASS_GET_DECLARED_METHOD:
       case CLASS_GET_DECLARED_FIELD:
       case CLASS_GET_DECLARED_CONSTRUCTOR:
@@ -123,13 +147,13 @@
       case CLASS_CAST:
         break;
       case CLASS_FLAG:
-        break;
+        return new ClassFlagEvent(eventType, stack, args, factory);
       case ATOMIC_FIELD_UPDATER_NEW_UPDATER:
         return new AtomicFieldUpdaterNewUpdater(eventType, stack, args, factory);
       case SERVICE_LOADER_LOAD:
         return new ServiceLoaderLoad(eventType, stack, args, factory);
       case PROXY_NEW_PROXY_INSTANCE:
-        break;
+        return new ProxyNewProxyInstance(eventType, stack, args, factory);
     }
     return new ReflectiveEvent(eventType, stack) {
       @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 5b17a26..da79f26 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -866,6 +866,8 @@
       createStaticallyKnownType("Ldalvik/annotation/NestHost;");
   public final DexType annotationNestMembers =
       createStaticallyKnownType("Ldalvik/annotation/NestMembers;");
+  public final DexType annotationNeverCompile =
+      createStaticallyKnownType("Ldalvik/annotation/optimization/NeverCompile;");
   public final DexType annotationPermittedSubclasses =
       createStaticallyKnownType("Ldalvik/annotation/PermittedSubclasses;");
   public final DexType annotationRecord = createStaticallyKnownType("Ldalvik/annotation/Record;");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 1711903..9459e5a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -755,7 +755,8 @@
               && (options.canUseJavaLangVarHandleStoreStoreFence(appView)
                   || inlinerOptions.skipStoreStoreFenceInConstructorInlining)
               && methodProcessor.hasWaves()
-              && target.isProgramField()) {
+              && target.isProgramField()
+              && appView.getKeepInfo(target.asProgramField()).isOptimizationAllowed(options)) {
             actionBuilder.setShouldEnsureStoreStoreFence(target.asProgramField());
           } else {
             whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToFinalFieldAssignment(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 482d5a1..0ae1c43 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -37,6 +37,8 @@
 import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
 import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfo;
+import com.android.tools.r8.shaking.MinimumKeepInfoCollection;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.DominatorChecker;
 import com.android.tools.r8.utils.ListUtils;
@@ -47,6 +49,7 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Predicate;
 
 /**
  * ServiceLoaderRewriter will attempt to rewrite calls on the form of: ServiceLoader.load(X.class,
@@ -107,9 +110,15 @@
   }
 
   private boolean shouldReportWhyAreYouNotInliningServiceLoaderLoad() {
-    AppInfoWithLiveness appInfo = appView().appInfo();
-    return appInfo.isWhyAreYouNotInliningMethod(serviceLoaderMethods.load)
-        || appInfo.isWhyAreYouNotInliningMethod(serviceLoaderMethods.loadWithClassLoader);
+    MinimumKeepInfoCollection keepInfo =
+        appView
+            .rootSet()
+            .getDependentMinimumKeepInfo()
+            .getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty());
+    Predicate<KeepInfo.Joiner<?, ?, ?>> test =
+        joiner -> joiner.asMethodJoiner().isWhyAreYouNotInliningEnabled();
+    return keepInfo.hasMinimumKeepInfoThatMatches(serviceLoaderMethods.load, test)
+        || keepInfo.hasMinimumKeepInfoThatMatches(serviceLoaderMethods.loadWithClassLoader, test);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index e70e49e..eeb46c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.Set;
 
@@ -21,7 +22,7 @@
 
   public static WhyAreYouNotInliningReporter createFor(
       ProgramMethod callee, AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
-    if (appView.appInfo().isWhyAreYouNotInliningMethod(callee.getReference())) {
+    if (appView.getKeepInfo(callee).isWhyAreYouNotInliningEnabled()) {
       return new WhyAreYouNotInliningReporterImpl(appView, callee, context);
     }
     return NopWhyAreYouNotInliningReporter.getInstance();
@@ -32,7 +33,9 @@
       InvokeMethod invoke,
       AppView<AppInfoWithLiveness> appView,
       ProgramMethod context) {
-    if (appView.appInfo().hasNoWhyAreYouNotInliningMethods()) {
+    InternalOptions options = appView.options();
+    if (!options.hasProguardConfiguration()
+        || !options.getProguardConfiguration().hasWhyAreYouNotInliningRule()) {
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
index 0af7d88..f868bc8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
@@ -3,11 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.outliner.exceptions;
 
+import static com.android.tools.r8.graph.DexAnnotation.VISIBILITY_BUILD;
 import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
 
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
@@ -253,6 +258,7 @@
             builder ->
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setAnnotations(createAnnotations(appView))
                     // TODO(b/434769547): The API level of the code may be higher than the min-api.
                     //   This currently doesn't matter since outlining runs after API outlining.
                     .setApiLevelForCode(
@@ -260,4 +266,16 @@
                     .setCode(methodSig -> lirCode)
                     .setProto(getOptimizedProto(appView.dexItemFactory())));
   }
+
+  private DexAnnotationSet createAnnotations(AppView<?> appView) {
+    if (appView.options().getThrowBlockOutlinerOptions().neverCompile) {
+      DexItemFactory factory = appView.dexItemFactory();
+      DexEncodedAnnotation encodedAnnotation =
+          new DexEncodedAnnotation(
+              factory.annotationNeverCompile, DexAnnotationElement.EMPTY_ARRAY);
+      DexAnnotation annotation = new DexAnnotation(VISIBILITY_BUILD, encodedAnnotation);
+      return DexAnnotationSet.create(new DexAnnotation[] {annotation});
+    }
+    return DexAnnotationSet.empty();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerOptions.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerOptions.java
index 55036d0..7f6ea7d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerOptions.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerOptions.java
@@ -27,6 +27,10 @@
       SystemPropertyUtils.parseSystemPropertyOrDefault(
           "com.android.tools.r8.throwblockoutliner.users", Integer.MAX_VALUE);
 
+  public boolean neverCompile =
+      SystemPropertyUtils.parseSystemPropertyOrDefault(
+          "com.android.tools.r8.throwblockoutliner.nevercompile", false);
+
   public Consumer<Collection<ThrowBlockOutline>> outlineConsumerForTesting = null;
   public Predicate<ThrowBlockOutline> outlineStrategyForTesting = null;
 
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java b/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java
index fa4d3ab..9ba68e1 100644
--- a/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java
+++ b/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.processkeeprules;
 
 import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.shaking.FilteredClassPath;
@@ -12,7 +11,11 @@
 import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser;
 import com.android.tools.r8.shaking.ProguardConfigurationParserConsumer;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.shaking.ProguardPathList;
+import com.android.tools.r8.shaking.WhyAreYouKeepingRule;
+import com.android.tools.r8.shaking.WhyAreYouNotInliningRule;
+import com.android.tools.r8.shaking.WhyAreYouNotObfuscatingRule;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
@@ -64,6 +67,19 @@
   }
 
   private void write(String string) {
+    internalWrite(string, false);
+  }
+
+  private void writeComment(ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+    writeComment(parser.getContentSince(positionStart));
+  }
+
+  private void writeComment(String string) {
+    ensureComment();
+    internalWrite(string, true);
+  }
+
+  private void internalWrite(String string, boolean beginCommentOnNewline) {
     int lastNewlineIndex = string.lastIndexOf('\n');
     if (lastNewlineIndex < 0) {
       appendToCurrentLine(string);
@@ -73,6 +89,9 @@
       consumer.accept(untilNewlineInclusive, reporter);
       // Due to the newline character we are no longer inside a comment.
       exitComment();
+      if (beginCommentOnNewline) {
+        ensureComment();
+      }
       // Emit everything after the newline character.
       String fromNewlineExclusive = string.substring(lastNewlineIndex + 1);
       appendToCurrentLine(fromNewlineExclusive);
@@ -80,20 +99,156 @@
   }
 
   @Override
-  public void addKeepAttributePatterns(
-      List<String> attributesPatterns,
-      Origin origin,
+  public void addAdaptClassStringsPattern(
+      ProguardClassNameList pattern,
       ProguardConfigurationSourceParser parser,
-      Position position,
       TextPosition positionStart) {
     ensureNewlineAfterComment();
     write(parser, positionStart);
   }
 
   @Override
-  public void addRule(ProguardConfigurationRule rule) {
+  public void addAdaptResourceFileContents(
+      ProguardPathList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
     ensureNewlineAfterComment();
-    write(rule.getSource());
+    write(parser, positionStart);
+  }
+
+  @Override
+  public void addAdaptResourceFilenames(
+      ProguardPathList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
+
+  @Override
+  public void addBaseDirectory(
+      Path baseDirectory, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
+
+  @Override
+  public void addDontNotePattern(
+      ProguardClassNameList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
+
+  @Override
+  public void addDontWarnPattern(
+      ProguardClassNameList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
+
+  @Override
+  public void addIgnoredOption(
+      String option, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
+
+  @Override
+  public void addInclude(
+      Path includePath, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
+
+  @Override
+  public void addLeadingBOM() {
+    appendToCurrentLine(Character.toString(StringUtils.BOM));
+  }
+
+  @Override
+  public void addInjars(
+      List<FilteredClassPath> filteredClassPaths,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
+
+  @Override
+  public void addKeepAttributePatterns(
+      List<String> attributesPatterns,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    ProguardKeepAttributes keepAttributes = ProguardKeepAttributes.fromPatterns(attributesPatterns);
+    if (keepAttributes.lineNumberTable
+        || keepAttributes.runtimeInvisibleAnnotations
+        || keepAttributes.runtimeInvisibleParameterAnnotations
+        || keepAttributes.runtimeInvisibleTypeAnnotations
+        || keepAttributes.sourceFile) {
+      // Comment out the -keepattributes rule.
+      writeComment(parser, positionStart);
+      // Unset the undesired attributes and expand the rule.
+      keepAttributes.lineNumberTable = false;
+      keepAttributes.runtimeInvisibleAnnotations = false;
+      keepAttributes.runtimeInvisibleParameterAnnotations = false;
+      keepAttributes.runtimeInvisibleTypeAnnotations = false;
+      keepAttributes.sourceFile = false;
+      ensureNewlineAfterComment();
+      write(keepAttributes.toString());
+    } else {
+      ensureNewlineAfterComment();
+      write(parser, positionStart);
+    }
+  }
+
+  @Override
+  public void addKeepKotlinMetadata(
+      ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
+
+  @Override
+  public void addKeepPackageNamesPattern(
+      ProguardClassNameList proguardClassNameList,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
+
+  @Override
+  public void addLibraryJars(
+      List<FilteredClassPath> filteredClassPaths,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
+
+  @Override
+  public void addParsedConfiguration(ProguardConfigurationSourceParser parser) {
+    assert parser.getPendingIncludes().isEmpty();
+  }
+
+  @Override
+  public void addRule(
+      ProguardConfigurationRule rule,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
+    if (rule instanceof WhyAreYouKeepingRule
+        || rule instanceof WhyAreYouNotInliningRule
+        || rule instanceof WhyAreYouNotObfuscatingRule) {
+      writeComment(parser, positionStart);
+    } else {
+      ensureNewlineAfterComment();
+      write(parser, positionStart);
+    }
   }
 
   @Override
@@ -102,126 +257,164 @@
   }
 
   @Override
-  public void disableObfuscation(Origin origin, Position position) {
-    ensureComment();
-    write("-dontobfuscate");
+  public void disableObfuscation(ProguardConfigurationSourceParser parser, Position position) {
+    writeComment("-dontobfuscate");
   }
 
   @Override
-  public void disableOptimization(Origin origin, Position position) {
-    ensureComment();
-    write("-dontoptimize");
+  public void disableOptimization(ProguardConfigurationSourceParser parser, Position position) {
+    writeComment("-dontoptimize");
   }
 
   @Override
-  public void disableShrinking(Origin origin, Position position) {
-    ensureComment();
-    write("-dontshrink");
+  public void disableShrinking(ProguardConfigurationSourceParser parser, Position position) {
+    writeComment("-dontshrink");
   }
 
   @Override
-  public void setRenameSourceFileAttribute(String s, Origin origin, Position position) {}
+  public void enableAllowAccessModification(
+      ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void addKeepPackageNamesPattern(ProguardClassNameList proguardClassNameList) {}
+  public void enableFlattenPackageHierarchy(
+      String packagePrefix,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void setKeepParameterNames(boolean b, Origin origin, Position position) {}
+  public void enableKeepDirectories(
+      ProguardPathList keepDirectoryPatterns,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
 
   @Override
-  public void enableKeepDirectories() {}
+  public void enablePrintConfiguration(
+      Path printConfigurationFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void addKeepDirectories(ProguardPathList proguardPathList) {}
+  public void enablePrintMapping(
+      Path printMappingFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void addParsedConfiguration(String s) {}
+  public void enablePrintSeeds(
+      Path printSeedsFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void setPrintUsage(boolean b) {}
+  public void enablePrintUsage(
+      Path printUsageFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void setPrintUsageFile(Path path) {}
+  public void enableProtoShrinking(
+      ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void enableProtoShrinking() {}
+  public void enableRepackageClasses(
+      String packagePrefix,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void setIgnoreWarnings(boolean b) {}
+  public void joinMaxRemovedAndroidLogLevel(
+      int maxRemovedAndroidLogLevel,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void addDontWarnPattern(ProguardClassNameList pattern) {}
+  public void setApplyMappingFile(
+      Path applyMappingFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void addDontNotePattern(ProguardClassNameList pattern) {}
+  public void setIgnoreWarnings(
+      ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
 
   @Override
-  public void enableAllowAccessModification(Origin origin, Position position) {}
+  public void setClassObfuscationDictionary(
+      Path path,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void enablePrintConfiguration(Origin origin, Position position) {}
+  public void setKeepParameterNames(
+      ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart) {
+    ensureNewlineAfterComment();
+    write(parser, positionStart);
+  }
 
   @Override
-  public void setPrintConfigurationFile(Path path) {}
+  public void setObfuscationDictionary(
+      Path path,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void enablePrintMapping(Origin origin, Position position) {}
+  public void setPackageObfuscationDictionary(
+      Path path,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
-  public void setPrintMappingFile(Path path) {}
-
-  @Override
-  public void setApplyMappingFile(Path path, Origin origin, Position position) {}
-
-  @Override
-  public void addInjars(
-      List<FilteredClassPath> filteredClassPaths, Origin origin, Position position) {}
-
-  @Override
-  public void addLibraryJars(
-      List<FilteredClassPath> filteredClassPaths, Origin origin, Position position) {}
-
-  @Override
-  public void setPrintSeeds(boolean b, Origin origin, Position position) {}
-
-  @Override
-  public void setSeedFile(Path path) {}
-
-  @Override
-  public void setObfuscationDictionary(Path path, Origin origin, Position position) {}
-
-  @Override
-  public void setClassObfuscationDictionary(Path path, Origin origin, Position position) {}
-
-  @Override
-  public void setPackageObfuscationDictionary(Path path, Origin origin, Position position) {}
-
-  @Override
-  public void addAdaptClassStringsPattern(ProguardClassNameList pattern) {}
-
-  @Override
-  public void addAdaptResourceFileContents(ProguardPathList pattern) {}
-
-  @Override
-  public void addAdaptResourceFilenames(ProguardPathList pattern) {}
-
-  @Override
-  public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {}
+  public void setRenameSourceFileAttribute(
+      String s,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    writeComment(parser, positionStart);
+  }
 
   @Override
   public PackageObfuscationMode getPackageObfuscationMode() {
     return null;
   }
-
-  @Override
-  public void setPackagePrefix(String s) {}
-
-  @Override
-  public void setFlattenPackagePrefix(String s) {}
-
-  @Override
-  public void enableRepackageClasses(Origin origin, Position position) {}
-
-  @Override
-  public void enableFlattenPackageHierarchy(Origin origin, Position position) {}
 }
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java b/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java
index c1f3d5a..3886317 100644
--- a/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java
@@ -12,8 +12,7 @@
 
   private final String attribute;
 
-  public KeepAttributeLibraryConsumerRuleDiagnostic(
-      Origin origin, Position position, String attribute) {
+  KeepAttributeLibraryConsumerRuleDiagnostic(Origin origin, Position position, String attribute) {
     super(origin, position);
     this.attribute = attribute;
   }
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java b/src/main/java/com/android/tools/r8/processkeeprules/LibraryConsumerRuleDiagnostic.java
similarity index 78%
rename from src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java
rename to src/main/java/com/android/tools/r8/processkeeprules/LibraryConsumerRuleDiagnostic.java
index bc1a21d..a3bf0c9 100644
--- a/src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/processkeeprules/LibraryConsumerRuleDiagnostic.java
@@ -8,10 +8,11 @@
 import com.android.tools.r8.position.Position;
 
 @KeepForApi
-public class GlobalLibraryConsumerRuleDiagnostic extends ConsumerRuleDiagnostic {
+public class LibraryConsumerRuleDiagnostic extends ConsumerRuleDiagnostic {
+
   private final String rule;
 
-  public GlobalLibraryConsumerRuleDiagnostic(Origin origin, Position position, String rule) {
+  LibraryConsumerRuleDiagnostic(Origin origin, Position position, String rule) {
     super(origin, position);
     this.rule = rule;
   }
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
index c2ced3e..e0bbeab 100644
--- a/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
+++ b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.processkeeprules;
 
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.shaking.FilteredClassPath;
@@ -13,12 +12,16 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.shaking.ProguardPathList;
+import com.android.tools.r8.shaking.WhyAreYouKeepingRule;
+import com.android.tools.r8.shaking.WhyAreYouNotInliningRule;
+import com.android.tools.r8.shaking.WhyAreYouNotObfuscatingRule;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.Reporter;
 import java.nio.file.Path;
 import java.util.List;
 
 class ValidateLibraryConsumerRulesKeepRuleProcessor implements ProguardConfigurationParserConsumer {
+
   private final Reporter reporter;
 
   public ValidateLibraryConsumerRulesKeepRuleProcessor(Reporter reporter) {
@@ -26,189 +29,286 @@
     this.reporter = reporter;
   }
 
-  private void handleGlobalRule(Origin origin, Position position, String rule) {
-    reporter.error(new GlobalLibraryConsumerRuleDiagnostic(origin, position, rule));
+  private void handleRule(
+      ProguardConfigurationSourceParser parser, Position position, String rule) {
+    reporter.error(new LibraryConsumerRuleDiagnostic(parser.getOrigin(), position, rule));
   }
 
-  private void handleKeepAttribute(Origin origin, Position position, String attribute) {
-    reporter.error(new KeepAttributeLibraryConsumerRuleDiagnostic(origin, position, attribute));
+  private void handleKeepAttribute(
+      ProguardConfigurationSourceParser parser, Position position, String attribute) {
+    reporter.error(
+        new KeepAttributeLibraryConsumerRuleDiagnostic(parser.getOrigin(), position, attribute));
   }
 
   @Override
-  public void disableOptimization(Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-dontoptimize");
+  public void disableOptimization(ProguardConfigurationSourceParser parser, Position position) {
+    handleRule(parser, position, "-dontoptimize");
   }
 
   @Override
-  public void disableObfuscation(Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-dontobfuscate");
+  public void disableObfuscation(ProguardConfigurationSourceParser parser, Position position) {
+    handleRule(parser, position, "-dontobfuscate");
   }
 
   @Override
-  public void disableShrinking(Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-dontshrink");
+  public void disableShrinking(ProguardConfigurationSourceParser parser, Position position) {
+    handleRule(parser, position, "-dontshrink");
   }
 
   @Override
-  public void enableRepackageClasses(Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-repackageclasses");
+  public void enableRepackageClasses(
+      String packagePrefix,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-repackageclasses");
   }
 
   @Override
-  public void enableFlattenPackageHierarchy(Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-flattenpackagehierarchy");
+  public void enableFlattenPackageHierarchy(
+      String packagePrefix,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-flattenpackagehierarchy");
   }
 
   @Override
-  public void enableAllowAccessModification(Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-allowaccessmodification");
+  public void enableAllowAccessModification(
+      ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart) {
+    handleRule(parser, position, "-allowaccessmodification");
   }
 
   @Override
-  public void setRenameSourceFileAttribute(String s, Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-renamesourcefileattribute");
+  public void setRenameSourceFileAttribute(
+      String s,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-renamesourcefileattribute");
   }
 
   @Override
-  public void addParsedConfiguration(String s) {}
+  public void addBaseDirectory(
+      Path baseDirectory, ProguardConfigurationSourceParser parser, TextPosition positionStart) {}
 
   @Override
-  public void addRule(ProguardConfigurationRule rule) {}
+  public void addIgnoredOption(
+      String option, ProguardConfigurationSourceParser parser, TextPosition positionStart) {}
+
+  @Override
+  public void addInclude(
+      Path includePath, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+    // TODO(b/270289387): Report error.
+  }
+
+  @Override
+  public void addLeadingBOM() {}
+
+  @Override
+  public void addParsedConfiguration(ProguardConfigurationSourceParser parser) {}
+
+  @Override
+  public void addRule(
+      ProguardConfigurationRule rule,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
+    if (rule instanceof WhyAreYouKeepingRule) {
+      handleRule(parser, positionStart, "-whyareyoukeeping");
+    } else if (rule instanceof WhyAreYouNotInliningRule) {
+      handleRule(parser, positionStart, "-whyareyounotinlining");
+    } else if (rule instanceof WhyAreYouNotObfuscatingRule) {
+      handleRule(parser, positionStart, "-whyareyounotobfuscating");
+    }
+  }
 
   @Override
   public void addKeepAttributePatterns(
       List<String> attributesPatterns,
-      Origin origin,
       ProguardConfigurationSourceParser parser,
       Position position,
       TextPosition positionStart) {
     // TODO(b/270289387): Add support for more attributes.
     ProguardKeepAttributes keepAttributes = ProguardKeepAttributes.fromPatterns(attributesPatterns);
     if (keepAttributes.lineNumberTable) {
-      handleKeepAttribute(origin, position, ProguardKeepAttributes.LINE_NUMBER_TABLE);
+      handleKeepAttribute(parser, position, ProguardKeepAttributes.LINE_NUMBER_TABLE);
     }
     if (keepAttributes.runtimeInvisibleAnnotations) {
-      handleKeepAttribute(origin, position, ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS);
+      handleKeepAttribute(parser, position, ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS);
     }
     if (keepAttributes.runtimeInvisibleTypeAnnotations) {
       handleKeepAttribute(
-          origin, position, ProguardKeepAttributes.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+          parser, position, ProguardKeepAttributes.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
     }
     if (keepAttributes.runtimeInvisibleParameterAnnotations) {
       handleKeepAttribute(
-          origin, position, ProguardKeepAttributes.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
+          parser, position, ProguardKeepAttributes.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
     }
     if (keepAttributes.sourceFile) {
-      handleKeepAttribute(origin, position, ProguardKeepAttributes.SOURCE_FILE);
+      handleKeepAttribute(parser, position, ProguardKeepAttributes.SOURCE_FILE);
     }
   }
 
   @Override
-  public void addKeepPackageNamesPattern(ProguardClassNameList proguardClassNameList) {}
+  public void addKeepKotlinMetadata(
+      ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart) {}
 
   @Override
-  public void setKeepParameterNames(boolean b, Origin origin, Position position) {}
+  public void addKeepPackageNamesPattern(
+      ProguardClassNameList proguardClassNameList,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {}
 
   @Override
-  public void enableKeepDirectories() {}
+  public void setKeepParameterNames(
+      ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart) {}
 
   @Override
-  public void addKeepDirectories(ProguardPathList proguardPathList) {}
+  public void enableKeepDirectories(
+      ProguardPathList keepDirectoryPatterns,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {}
 
   @Override
-  public void setPrintUsage(boolean b) {}
-
-  @Override
-  public void setPrintUsageFile(Path path) {}
-
-  @Override
-  public void enableProtoShrinking() {}
-
-  @Override
-  public void setIgnoreWarnings(boolean b) {}
-
-  @Override
-  public void addDontWarnPattern(ProguardClassNameList pattern) {}
-
-  @Override
-  public void addDontNotePattern(ProguardClassNameList pattern) {}
-
-  @Override
-  public void enablePrintConfiguration(Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-printconfiguration");
+  public void enableProtoShrinking(
+      ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+    handleRule(parser, positionStart, "-shrinkunusedprotofields");
   }
 
   @Override
-  public void setPrintConfigurationFile(Path path) {}
+  public void setIgnoreWarnings(
+      ProguardConfigurationSourceParser parser, TextPosition positionStart) {}
 
   @Override
-  public void enablePrintMapping(Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-printmapping");
+  public void addDontWarnPattern(
+      ProguardClassNameList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {}
+
+  @Override
+  public void addDontNotePattern(
+      ProguardClassNameList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {}
+
+  @Override
+  public void enablePrintConfiguration(
+      Path printConfigurationFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-printconfiguration");
   }
 
   @Override
-  public void setPrintMappingFile(Path path) {}
+  public void enablePrintMapping(
+      Path printMappingFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-printmapping");
+  }
 
   @Override
-  public void setApplyMappingFile(Path path, Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-applymapping");
+  public void enablePrintSeeds(
+      Path printSeedsFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-printseeds");
+  }
+
+  @Override
+  public void enablePrintUsage(
+      Path printUsageFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-printusage");
+  }
+
+  @Override
+  public void setApplyMappingFile(
+      Path applyMappingFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-applymapping");
   }
 
   @Override
   public void addInjars(
-      List<FilteredClassPath> filteredClassPaths, Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-injars");
+      List<FilteredClassPath> filteredClassPaths,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-injars");
   }
 
   @Override
   public void addLibraryJars(
-      List<FilteredClassPath> filteredClassPaths, Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-libraryjars");
+      List<FilteredClassPath> filteredClassPaths,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-libraryjars");
   }
 
   @Override
-  public void setPrintSeeds(boolean b, Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-printseeds");
+  public void setObfuscationDictionary(
+      Path path,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-obfuscationdictionary");
   }
 
   @Override
-  public void setSeedFile(Path path) {}
-
-  @Override
-  public void setObfuscationDictionary(Path path, Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-obfuscationdictionary");
+  public void setClassObfuscationDictionary(
+      Path path,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-classobfuscationdictionary");
   }
 
   @Override
-  public void setClassObfuscationDictionary(Path path, Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-classobfuscationdictionary");
+  public void setPackageObfuscationDictionary(
+      Path path,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart) {
+    handleRule(parser, position, "-packageobfuscationdictionary");
   }
 
   @Override
-  public void setPackageObfuscationDictionary(Path path, Origin origin, Position position) {
-    handleGlobalRule(origin, position, "-packageobfuscationdictionary");
+  public void addAdaptClassStringsPattern(
+      ProguardClassNameList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {}
+
+  @Override
+  public void addAdaptResourceFileContents(
+      ProguardPathList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {}
+
+  @Override
+  public void addAdaptResourceFilenames(
+      ProguardPathList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {}
+
+  @Override
+  public void joinMaxRemovedAndroidLogLevel(
+      int maxRemovedAndroidLogLevel,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart) {
+    handleRule(parser, positionStart, "-maximumremovedandroidloglevel <int>");
   }
 
   @Override
-  public void addAdaptClassStringsPattern(ProguardClassNameList pattern) {}
-
-  @Override
-  public void addAdaptResourceFileContents(ProguardPathList pattern) {}
-
-  @Override
-  public void addAdaptResourceFilenames(ProguardPathList pattern) {}
-
-  @Override
-  public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {}
-
-  @Override
   public PackageObfuscationMode getPackageObfuscationMode() {
     return null;
   }
-
-  @Override
-  public void setPackagePrefix(String s) {}
-
-  @Override
-  public void setFlattenPackagePrefix(String s) {}
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 25f2a95..cb5e4b8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -147,8 +147,6 @@
   public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
   /** All methods that should be inlined if possible due to a configuration directive. */
   private final Set<DexMethod> alwaysInline;
-  /** Items for which to print inlining decisions for (testing only). */
-  private final Set<DexMethod> whyAreYouNotInlining;
   /** All methods that must be reprocessed (testing only). */
   private final Set<DexMethod> reprocess;
   /** All types that should be inlined if possible due to a configuration directive. */
@@ -210,7 +208,6 @@
       KeepInfoCollection keepInfo,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
       Set<DexMethod> alwaysInline,
-      Set<DexMethod> whyAreYouNotInlining,
       Set<DexMethod> reprocess,
       PredicateSet<DexType> alwaysClassInline,
       IdentifierNameStringCollection identifierNameStrings,
@@ -236,7 +233,6 @@
     this.mayHaveSideEffects = mayHaveSideEffects;
     this.callSites = callSites;
     this.alwaysInline = alwaysInline;
-    this.whyAreYouNotInlining = whyAreYouNotInlining;
     this.reprocess = reprocess;
     this.alwaysClassInline = alwaysClassInline;
     this.identifierNameStrings = identifierNameStrings;
@@ -270,7 +266,6 @@
         previous.keepInfo,
         previous.mayHaveSideEffects,
         previous.alwaysInline,
-        previous.whyAreYouNotInlining,
         previous.reprocess,
         previous.alwaysClassInline,
         previous.identifierNameStrings,
@@ -305,7 +300,6 @@
         extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()),
         previous.mayHaveSideEffects,
         pruneMethods(previous.alwaysInline, prunedItems, tasks),
-        pruneMethods(previous.whyAreYouNotInlining, prunedItems, tasks),
         pruneMethods(previous.reprocess, prunedItems, tasks),
         previous.alwaysClassInline,
         previous.identifierNameStrings.prune(prunedItems, tasks),
@@ -431,7 +425,6 @@
         keepInfo,
         mayHaveSideEffects,
         alwaysInline,
-        whyAreYouNotInlining,
         reprocess,
         alwaysClassInline,
         identifierNameStrings,
@@ -502,7 +495,6 @@
     this.mayHaveSideEffects = previous.mayHaveSideEffects;
     this.callSites = previous.callSites;
     this.alwaysInline = previous.alwaysInline;
-    this.whyAreYouNotInlining = previous.whyAreYouNotInlining;
     this.reprocess = previous.reprocess;
     this.alwaysClassInline = previous.alwaysClassInline;
     this.identifierNameStrings = previous.identifierNameStrings;
@@ -638,14 +630,6 @@
     return alwaysInline.contains(method);
   }
 
-  public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
-    return whyAreYouNotInlining.contains(method);
-  }
-
-  public boolean hasNoWhyAreYouNotInliningMethods() {
-    return whyAreYouNotInlining.isEmpty();
-  }
-
   public Set<DexMethod> getReprocessMethods() {
     return reprocess;
   }
@@ -999,7 +983,6 @@
         // Take any rule in case of collisions.
         lens.rewriteReferenceKeys(mayHaveSideEffects, (reference, rules) -> ListUtils.first(rules)),
         lens.rewriteReferences(alwaysInline),
-        lens.rewriteReferences(whyAreYouNotInlining),
         lens.rewriteReferences(reprocess),
         alwaysClassInline.rewriteItems(lens::lookupType),
         identifierNameStrings.rewrittenWithLens(lens),
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index e5cb200..4482352 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -506,7 +506,7 @@
     this.options = options;
     this.taskCollection =
         new EnqueuerTaskCollection(this, options.getThreadingModule(), executorService);
-    this.keepInfo = new MutableKeepInfoCollection(options);
+    this.keepInfo = new MutableKeepInfoCollection(appView, this);
     this.reflectiveIdentification = new EnqueuerReflectiveIdentification(appView, this);
     this.useRegistryFactory = createUseRegistryFactory();
     this.worklist = EnqueuerWorklist.createWorklist(this, options.getThreadingModule());
@@ -4746,7 +4746,6 @@
             getKeepInfo(),
             rootSet.mayHaveSideEffects,
             amendWithCompanionMethods(rootSet.alwaysInline),
-            amendWithCompanionMethods(rootSet.whyAreYouNotInlining),
             amendWithCompanionMethods(rootSet.reprocess),
             rootSet.alwaysClassInline,
             new IdentifierNameStringCollection(
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 3eca5de..4c4e47b 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -31,6 +31,7 @@
   private final boolean allowShrinking;
   private final boolean allowSignatureRemoval;
   private final boolean checkDiscarded;
+  private final boolean whyAreYouNotObfuscating;
   private final KeepAnnotationCollectionInfo annotationsInfo;
   private final KeepAnnotationCollectionInfo typeAnnotationsInfo;
 
@@ -42,6 +43,7 @@
       boolean allowShrinking,
       boolean allowSignatureRemoval,
       boolean checkDiscarded,
+      boolean whyAreYouNotObfuscating,
       KeepAnnotationCollectionInfo annotationsInfo,
       KeepAnnotationCollectionInfo typeAnnotationsInfo) {
     this.allowAccessModification = allowAccessModification;
@@ -51,6 +53,7 @@
     this.allowShrinking = allowShrinking;
     this.allowSignatureRemoval = allowSignatureRemoval;
     this.checkDiscarded = checkDiscarded;
+    this.whyAreYouNotObfuscating = whyAreYouNotObfuscating;
     this.annotationsInfo = annotationsInfo;
     this.typeAnnotationsInfo = typeAnnotationsInfo;
   }
@@ -64,6 +67,7 @@
         builder.isShrinkingAllowed(),
         builder.isSignatureRemovalAllowed(),
         builder.isCheckDiscardedEnabled(),
+        builder.isWhyAreYouNotObfuscatingEnabled(),
         builder.getAnnotationsInfo().build(),
         builder.getTypeAnnotationsInfo().build());
   }
@@ -150,6 +154,14 @@
     return checkDiscarded;
   }
 
+  public boolean isWhyAreYouNotObfuscatingEnabled() {
+    return internalIsWhyAreYouNotObfuscatingEnabled();
+  }
+
+  boolean internalIsWhyAreYouNotObfuscatingEnabled() {
+    return whyAreYouNotObfuscating;
+  }
+
   /**
    * True if an item must be present in the output.
    *
@@ -299,6 +311,7 @@
         && (allowShrinking || !other.internalIsShrinkingAllowed())
         && (allowSignatureRemoval || !other.internalIsSignatureRemovalAllowed())
         && (!checkDiscarded || other.internalIsCheckDiscardedEnabled())
+        && (!whyAreYouNotObfuscating || other.internalIsWhyAreYouNotObfuscatingEnabled())
         && annotationsInfo.isLessThanOrEqualTo(other.internalAnnotationsInfo())
         && typeAnnotationsInfo.isLessThanOrEqualTo(other.internalTypeAnnotationsInfo());
   }
@@ -312,7 +325,8 @@
         && allowOptimization == other.internalIsOptimizationAllowed()
         && allowShrinking == other.internalIsShrinkingAllowed()
         && allowSignatureRemoval == other.internalIsSignatureRemovalAllowed()
-        && checkDiscarded == other.internalIsCheckDiscardedEnabled();
+        && checkDiscarded == other.internalIsCheckDiscardedEnabled()
+        && whyAreYouNotObfuscating == other.internalIsWhyAreYouNotObfuscatingEnabled();
   }
 
   public boolean equalsWithAnnotations(K other) {
@@ -341,6 +355,7 @@
     hash += bit(allowShrinking, index++);
     hash += bit(allowSignatureRemoval, index++);
     hash += bit(checkDiscarded, index++);
+    hash += bit(whyAreYouNotObfuscating, index++);
     hash += bit(annotationsInfo.isTop(), index++);
     hash += bit(typeAnnotationsInfo.isTop(), index);
     return hash;
@@ -369,6 +384,9 @@
       case "checkDiscarded":
         builder.setCheckDiscarded(Boolean.parseBoolean(value));
         return true;
+      case "whyAreYouNotObfuscating":
+        builder.setWhyAreYouNotObfuscating(Boolean.parseBoolean(value));
+        return true;
       case "annotationsInfo":
         builder.setAnnotationInfo(KeepAnnotationCollectionInfoExported.parse(value));
         return true;
@@ -389,6 +407,7 @@
     lines.add("allowShrinking: " + allowShrinking);
     lines.add("allowSignatureRemoval: " + allowSignatureRemoval);
     lines.add("checkDiscarded: " + checkDiscarded);
+    lines.add("whyAreYouNotObfuscating: " + whyAreYouNotObfuscating);
     lines.add("annotationsInfo: " + annotationsInfo);
     lines.add("typeAnnotationsInfo: " + typeAnnotationsInfo);
     return lines;
@@ -423,6 +442,7 @@
     private boolean allowShrinking;
     private boolean allowSignatureRemoval;
     private boolean checkDiscarded;
+    private boolean whyAreYouNotObfuscating;
     private KeepAnnotationCollectionInfo.Builder annotationsInfo;
     private KeepAnnotationCollectionInfo.Builder typeAnnotationsInfo;
 
@@ -439,6 +459,7 @@
       allowShrinking = original.internalIsShrinkingAllowed();
       allowSignatureRemoval = original.internalIsSignatureRemovalAllowed();
       checkDiscarded = original.internalIsCheckDiscardedEnabled();
+      whyAreYouNotObfuscating = original.internalIsWhyAreYouNotObfuscatingEnabled();
       annotationsInfo = original.internalAnnotationsInfo().toBuilder();
       typeAnnotationsInfo = original.internalTypeAnnotationsInfo().toBuilder();
     }
@@ -453,6 +474,7 @@
       setAllowShrinking(false);
       setAllowSignatureRemoval(false);
       setCheckDiscarded(false);
+      setWhyAreYouNotObfuscating(false);
       return self();
     }
 
@@ -466,6 +488,7 @@
       setAllowShrinking(true);
       setAllowSignatureRemoval(true);
       setCheckDiscarded(false);
+      setWhyAreYouNotObfuscating(false);
       return self();
     }
 
@@ -493,6 +516,7 @@
           && isShrinkingAllowed() == other.internalIsShrinkingAllowed()
           && isSignatureRemovalAllowed() == other.internalIsSignatureRemovalAllowed()
           && isCheckDiscardedEnabled() == other.internalIsCheckDiscardedEnabled()
+          && isWhyAreYouNotObfuscatingEnabled() == other.internalIsWhyAreYouNotObfuscatingEnabled()
           && annotationsInfo.isEqualTo(other.internalAnnotationsInfo())
           && typeAnnotationsInfo.isEqualTo(other.internalTypeAnnotationsInfo());
     }
@@ -506,6 +530,15 @@
       return self();
     }
 
+    public boolean isWhyAreYouNotObfuscatingEnabled() {
+      return whyAreYouNotObfuscating;
+    }
+
+    public B setWhyAreYouNotObfuscating(boolean whyAreYouNotObfuscating) {
+      this.whyAreYouNotObfuscating = whyAreYouNotObfuscating;
+      return self();
+    }
+
     public boolean isMinificationAllowed() {
       return allowMinification;
     }
@@ -654,6 +687,10 @@
       return builder.isCheckDiscardedEnabled();
     }
 
+    public boolean isWhyAreYouNotObfuscatingEnabled() {
+      return builder.isWhyAreYouNotObfuscatingEnabled();
+    }
+
     public boolean isMinificationAllowed() {
       return builder.isMinificationAllowed();
     }
@@ -735,6 +772,11 @@
       return self();
     }
 
+    public J setWhyAreYouNotObfuscating() {
+      builder.setWhyAreYouNotObfuscating(true);
+      return self();
+    }
+
     public J merge(J joiner) {
       Builder<B, K> otherBuilder = joiner.builder;
       applyIf(!otherBuilder.isAccessModificationAllowed(), Joiner::disallowAccessModification);
@@ -746,6 +788,7 @@
       applyIf(!otherBuilder.isShrinkingAllowed(), Joiner::disallowShrinking);
       applyIf(!otherBuilder.isSignatureRemovalAllowed(), Joiner::disallowSignatureRemoval);
       applyIf(otherBuilder.isCheckDiscardedEnabled(), Joiner::setCheckDiscarded);
+      applyIf(otherBuilder.isWhyAreYouNotObfuscatingEnabled(), Joiner::setWhyAreYouNotObfuscating);
       builder.getAnnotationsInfo().destructiveJoin(otherBuilder.getAnnotationsInfo());
       builder.getTypeAnnotationsInfo().destructiveJoin(otherBuilder.getTypeAnnotationsInfo());
       reasons.addAll(joiner.reasons);
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 11ebdfb..574dc74 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -284,7 +284,9 @@
   // Mutation interface for building up the keep info.
   public static class MutableKeepInfoCollection extends KeepInfoCollection {
 
+    private final AppView<?> appView;
     private final DexItemFactory factory;
+    private final Enqueuer.Mode mode;
 
     // These are typed at signatures but the interface should make sure never to allow access
     // directly with a signature. See the comment in KeepInfoCollection.
@@ -304,9 +306,10 @@
 
     private final KeepInfoCanonicalizer canonicalizer;
 
-    MutableKeepInfoCollection(InternalOptions options) {
+    MutableKeepInfoCollection(AppView<?> appView, Enqueuer enqueuer) {
       this(
-          options,
+          appView,
+          enqueuer.getMode(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
@@ -314,13 +317,14 @@
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
           MaterializedRules.empty(),
-          options.testing.enableKeepInfoCanonicalizer
+          appView.testing().enableKeepInfoCanonicalizer
               ? KeepInfoCanonicalizer.newCanonicalizer()
               : KeepInfoCanonicalizer.newNopCanonicalizer());
     }
 
     private MutableKeepInfoCollection(
-        InternalOptions options,
+        AppView<?> appView,
+        Enqueuer.Mode mode,
         Map<DexType, KeepClassInfo> keepClassInfo,
         Map<DexMethod, KeepMethodInfo> keepMethodInfo,
         Map<DexField, KeepFieldInfo> keepFieldInfo,
@@ -329,7 +333,9 @@
         Map<DexMethod, KeepMethodInfo.Joiner> methodRuleInstances,
         MaterializedRules materializedRules,
         KeepInfoCanonicalizer keepInfoCanonicalizer) {
-      this.factory = options.dexItemFactory();
+      this.appView = appView;
+      this.factory = appView.dexItemFactory();
+      this.mode = mode;
       this.keepClassInfo = keepClassInfo;
       this.keepMethodInfo = keepMethodInfo;
       this.keepFieldInfo = keepFieldInfo;
@@ -388,7 +394,8 @@
       Map<DexField, KeepFieldInfo> newFieldInfo = rewriteFieldInfo(lens, options, timing);
       MutableKeepInfoCollection result =
           new MutableKeepInfoCollection(
-              options,
+              appView,
+              mode,
               newClassInfo,
               newMethodInfo,
               newFieldInfo,
@@ -610,10 +617,12 @@
       KeepClassInfo.Joiner joiner = info.joiner();
       fn.accept(joiner);
       KeepClassInfo joined = joiner.join();
-      if (!info.equals(joined)) {
-        keepClassInfo.put(clazz.type, canonicalizer.canonicalizeKeepClassInfo(joined));
-        maybeDisallowKotlinMetadataRemoval(clazz, info, joined, joiner);
+      if (info.equals(joined)) {
+        return;
       }
+      keepClassInfo.put(clazz.type, canonicalizer.canonicalizeKeepClassInfo(joined));
+      maybeDisallowKotlinMetadataRemoval(clazz, info, joined, joiner);
+      reportWhyAreYouNotObfuscating(clazz, info, joined, joiner);
     }
 
     private void maybeDisallowKotlinMetadataRemoval(
@@ -639,6 +648,54 @@
       allowKotlinMetadataRemoval = false;
     }
 
+    private void reportWhyAreYouNotObfuscating(
+        ProgramDefinition definition,
+        KeepInfo<?, ?> previousKeepInfo,
+        KeepInfo<?, ?> joinedKeepInfo,
+        KeepInfo.Joiner<?, ?, ?> joiner) {
+      InternalOptions options = appView.options();
+      if (!mode.isFinalTreeShaking()
+          || options.getProguardConfiguration() == null
+          || !options.getProguardConfiguration().hasWhyAreYouNotObfuscatingRule()
+          || !previousKeepInfo.internalIsMinificationAllowed()
+          || joiner.isMinificationAllowed()) {
+        return;
+      }
+      MinimumKeepInfoCollection minimumKeepInfoCollection =
+          appView
+              .rootSet()
+              .getDependentMinimumKeepInfo()
+              .getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty());
+      if (!minimumKeepInfoCollection.hasMinimumKeepInfoThatMatches(
+          definition.getReference(), KeepInfo.Joiner::isWhyAreYouNotObfuscatingEnabled)) {
+        return;
+      }
+      assert !joinedKeepInfo.internalIsMinificationAllowed();
+      boolean foundRule = false;
+      for (ProguardKeepRuleBase rule : joiner.getRules()) {
+        if (!rule.getModifiers().allowsObfuscation) {
+          appView
+              .reporter()
+              .warning(
+                  definition.getReference().toSourceString() + " is not obfuscated due to " + rule);
+          foundRule = true;
+        }
+      }
+      if (!foundRule) {
+        boolean foundReason = false;
+        for (KeepReason reason : joiner.getReasons()) {
+          appView
+              .reporter()
+              .warning(
+                  definition.getReference().toSourceString()
+                      + " is not obfuscated due to "
+                      + reason);
+          foundReason = true;
+        }
+        assert foundReason;
+      }
+    }
+
     public void keepClass(DexProgramClass clazz) {
       joinClass(clazz, KeepInfo.Joiner::top);
     }
@@ -652,9 +709,11 @@
       KeepMethodInfo.Joiner joiner = info.joiner();
       fn.accept(joiner);
       KeepMethodInfo joined = joiner.join();
-      if (!info.equals(joined)) {
-        keepMethodInfo.put(method.getReference(), canonicalizer.canonicalizeKeepMethodInfo(joined));
+      if (info.equals(joined)) {
+        return;
       }
+      keepMethodInfo.put(method.getReference(), canonicalizer.canonicalizeKeepMethodInfo(joined));
+      reportWhyAreYouNotObfuscating(method, info, joined, joiner);
     }
 
     public void keepMethod(ProgramMethod method) {
@@ -670,9 +729,11 @@
       Joiner joiner = info.joiner();
       fn.accept(joiner);
       KeepFieldInfo joined = joiner.join();
-      if (!info.equals(joined)) {
-        keepFieldInfo.put(field.getReference(), canonicalizer.canonicalizeKeepFieldInfo(joined));
+      if (info.equals(joined)) {
+        return;
       }
+      keepFieldInfo.put(field.getReference(), canonicalizer.canonicalizeKeepFieldInfo(joined));
+      reportWhyAreYouNotObfuscating(field, info, joined, joiner);
     }
 
     public void keepField(ProgramField field) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index 597b016..a9879ad 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -48,6 +48,7 @@
   private final boolean allowUnusedArgumentOptimization;
   private final boolean allowUnusedReturnValueOptimization;
   private final boolean allowParameterNamesRemoval;
+  private final boolean whyAreYouNotInlining;
   private final KeepAnnotationCollectionInfo parameterAnnotationsInfo;
 
   protected KeepMethodInfo(Builder builder) {
@@ -68,6 +69,7 @@
     this.allowUnusedArgumentOptimization = builder.isUnusedArgumentOptimizationAllowed();
     this.allowUnusedReturnValueOptimization = builder.isUnusedReturnValueOptimizationAllowed();
     this.allowParameterNamesRemoval = builder.isParameterNamesRemovalAllowed();
+    this.whyAreYouNotInlining = builder.isWhyAreYouNotInliningEnabled();
     this.parameterAnnotationsInfo = builder.getParameterAnnotationsInfo().build();
   }
 
@@ -293,6 +295,14 @@
     return allowParameterNamesRemoval;
   }
 
+  public boolean isWhyAreYouNotInliningEnabled() {
+    return internalIsWhyAreYouNotInliningEnabled();
+  }
+
+  boolean internalIsWhyAreYouNotInliningEnabled() {
+    return whyAreYouNotInlining;
+  }
+
   public Joiner joiner() {
     assert !isTop();
     return new Joiner(this);
@@ -326,7 +336,8 @@
         && allowUnusedArgumentOptimization == other.internalIsUnusedArgumentOptimizationAllowed()
         && allowUnusedReturnValueOptimization
             == other.internalIsUnusedReturnValueOptimizationAllowed()
-        && allowParameterNamesRemoval == other.internalIsParameterNamesRemovalAllowed();
+        && allowParameterNamesRemoval == other.internalIsParameterNamesRemovalAllowed()
+        && whyAreYouNotInlining == other.internalIsWhyAreYouNotInliningEnabled();
   }
 
   @Override
@@ -365,6 +376,7 @@
     hash += bit(allowUnusedArgumentOptimization, index++);
     hash += bit(allowUnusedReturnValueOptimization, index++);
     hash += bit(allowParameterNamesRemoval, index++);
+    hash += bit(whyAreYouNotInlining, index++);
     hash += bit(parameterAnnotationsInfo.isTop(), index);
     return hash;
   }
@@ -432,6 +444,9 @@
         case "allowParameterNamesRemoval":
           builder.setAllowParameterNamesRemoval(Boolean.parseBoolean(value));
           break;
+        case "whyAreYouNotInlining":
+          builder.setWhyAreYouNotInlining(Boolean.parseBoolean(value));
+          break;
         case "parameterAnnotationsInfo":
           builder.setParameterAnnotationInfo(KeepAnnotationCollectionInfoExported.parse(value));
           break;
@@ -462,6 +477,7 @@
     lines.add("allowUnusedArgumentOptimization: " + allowUnusedArgumentOptimization);
     lines.add("allowUnusedReturnValueOptimization: " + allowUnusedReturnValueOptimization);
     lines.add("allowParameterNamesRemoval: " + allowParameterNamesRemoval);
+    lines.add("whyAreYouNotInlining: " + whyAreYouNotInlining);
     lines.add("parameterAnnotationsInfo: " + parameterAnnotationsInfo);
     return lines;
   }
@@ -484,6 +500,7 @@
     private boolean allowUnusedArgumentOptimization;
     private boolean allowUnusedReturnValueOptimization;
     private boolean allowParameterNamesRemoval;
+    private boolean whyAreYouNotInlining;
     private KeepAnnotationCollectionInfo.Builder parameterAnnotationsInfo;
 
     public Builder() {
@@ -509,6 +526,7 @@
       allowUnusedReturnValueOptimization =
           original.internalIsUnusedReturnValueOptimizationAllowed();
       allowParameterNamesRemoval = original.internalIsParameterNamesRemovalAllowed();
+      whyAreYouNotInlining = original.internalIsWhyAreYouNotInliningEnabled();
       parameterAnnotationsInfo = original.internalParameterAnnotationsInfo().toBuilder();
     }
 
@@ -657,6 +675,15 @@
       return self();
     }
 
+    public boolean isWhyAreYouNotInliningEnabled() {
+      return whyAreYouNotInlining;
+    }
+
+    public Builder setWhyAreYouNotInlining(boolean whyAreYouNotInlining) {
+      this.whyAreYouNotInlining = whyAreYouNotInlining;
+      return self();
+    }
+
     public KeepAnnotationCollectionInfo.Builder getParameterAnnotationsInfo() {
       return parameterAnnotationsInfo;
     }
@@ -709,6 +736,7 @@
           && isUnusedReturnValueOptimizationAllowed()
               == other.internalIsUnusedReturnValueOptimizationAllowed()
           && isParameterNamesRemovalAllowed() == other.internalIsParameterNamesRemovalAllowed()
+          && isWhyAreYouNotInliningEnabled() == other.internalIsWhyAreYouNotInliningEnabled()
           && parameterAnnotationsInfo.isEqualTo(other.parameterAnnotationsInfo);
     }
 
@@ -736,6 +764,7 @@
           .setAllowUnusedArgumentOptimization(false)
           .setAllowUnusedReturnValueOptimization(false)
           .setAllowParameterNamesRemoval(false)
+          .setWhyAreYouNotInlining(false)
           .setParameterAnnotationInfo(KeepAnnotationCollectionInfo.Builder.createTop());
     }
 
@@ -758,6 +787,7 @@
           .setAllowUnusedArgumentOptimization(true)
           .setAllowUnusedReturnValueOptimization(true)
           .setAllowParameterNamesRemoval(true)
+          .setWhyAreYouNotInlining(false)
           .setParameterAnnotationInfo(KeepAnnotationCollectionInfo.Builder.createBottom());
     }
   }
@@ -862,6 +892,15 @@
       return self();
     }
 
+    public boolean isWhyAreYouNotInliningEnabled() {
+      return builder.isWhyAreYouNotInliningEnabled();
+    }
+
+    public Joiner setWhyAreYouNotInlining() {
+      builder.setWhyAreYouNotInlining(true);
+      return self();
+    }
+
     @Override
     public Joiner asMethodJoiner() {
       return this;
@@ -904,7 +943,8 @@
               Joiner::disallowUnusedReturnValueOptimization)
           .applyIf(
               !joiner.builder.isParameterNamesRemovalAllowed(),
-              Joiner::disallowParameterNamesRemoval);
+              Joiner::disallowParameterNamesRemoval)
+          .applyIf(joiner.builder.isWhyAreYouNotInliningEnabled(), Joiner::setWhyAreYouNotInlining);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
index a2d6e00..755a476 100644
--- a/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
@@ -7,13 +7,13 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
+import com.android.tools.r8.graph.Definition;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
@@ -133,15 +133,11 @@
         minimumKeepInfo,
         (reference, minimumKeepInfoForReference) -> {
           assert !minimumKeepInfoForReference.isBottom();
-          ProgramDefinition definition =
+          Definition definition =
               reference.apply(
-                  clazz -> asProgramClassOrNull(definitions.definitionFor(clazz)),
-                  field ->
-                      field.lookupOnProgramClass(
-                          asProgramClassOrNull(definitions.definitionFor(field.getHolderType()))),
-                  method ->
-                      method.lookupOnProgramClass(
-                          asProgramClassOrNull(definitions.definitionFor(method.getHolderType()))));
+                  definitions::definitionFor,
+                  definitions::definitionFor,
+                  definitions::definitionFor);
           return definition == null || !enqueuer.isReachable(definition);
         });
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 5b0c3f1..4e47c83 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -3,12 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.shaking.ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS;
+import static com.android.tools.r8.shaking.ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS;
+
 import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.DictionaryReader;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.shaking.ProguardConfigurationParser.IncludeWorkItem;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.Reporter;
@@ -18,6 +22,7 @@
 import com.google.common.collect.Sets;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -26,7 +31,7 @@
 
   public static class Builder implements ProguardConfigurationParserConsumer {
 
-    private final List<String> parsedConfiguration = new ArrayList<>();
+    private final StringBuilder parsedConfiguration = new StringBuilder();
     private final List<FilteredClassPath> injars = new ArrayList<>();
 
     private final List<FilteredClassPath> libraryJars = new ArrayList<>();
@@ -53,13 +58,11 @@
     protected final Set<ProguardConfigurationRule> rules = Sets.newLinkedHashSet();
     private final DexItemFactory dexItemFactory;
     private boolean printSeeds;
-    private Path seedFile;
+    private Path printSeedsFile;
     private Path obfuscationDictionary;
     private Path classObfuscationDictionary;
     private Path packageObfuscationDictionary;
     private boolean keepParameterNames;
-    private Origin keepParameterNamesOptionOrigin;
-    private Position keepParameterNamesOptionPosition;
     private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
     private final ProguardPathFilter.Builder adaptResourceFilenames =
         ProguardPathFilter.builder()
@@ -85,43 +88,90 @@
     }
 
     @Override
-    public void addParsedConfiguration(String source) {
-      parsedConfiguration.add(source);
+    public void addBaseDirectory(
+        Path baseDirectory, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+      // Intentionally empty.
     }
 
     @Override
-    public void addInjars(List<FilteredClassPath> injars, Origin origin, Position position) {
+    public void addIgnoredOption(
+        String option, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void addInclude(
+        Path includePath, ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+      IncludeWorkItem include = new IncludeWorkItem(includePath, positionStart, parser.getOffset());
+      parser.getPendingIncludes().add(include);
+    }
+
+    @Override
+    public void addLeadingBOM() {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void addParsedConfiguration(ProguardConfigurationSourceParser parser) {
+      parsedConfiguration.append(
+          "# The proguard configuration file for the following section is " + parser.getOrigin());
+      parsedConfiguration.append(System.lineSeparator());
+      int lastIncludePositionEnd = 0;
+      for (IncludeWorkItem pendingInclude : parser.getPendingIncludes()) {
+        int includePositionStart = pendingInclude.includePositionStart.getOffsetAsInt();
+        parsedConfiguration.append(
+            parser.getContentInRange(lastIncludePositionEnd, includePositionStart));
+        lastIncludePositionEnd = pendingInclude.includePositionEnd;
+      }
+      parsedConfiguration.append(parser.getContentAfter(lastIncludePositionEnd));
+      parsedConfiguration.append(System.lineSeparator());
+      parsedConfiguration.append("# End of content from ");
+      parsedConfiguration.append(parser.getOrigin());
+      parsedConfiguration.append(System.lineSeparator());
+    }
+
+    @Override
+    public void addInjars(
+        List<FilteredClassPath> injars,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       this.injars.addAll(injars);
     }
 
     @Override
     public void addLibraryJars(
-        List<FilteredClassPath> libraryJars, Origin origin, Position position) {
+        List<FilteredClassPath> libraryJars,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       this.libraryJars.addAll(libraryJars);
     }
 
     @Override
-    public void enableAllowAccessModification(Origin origin, Position position) {
+    public void enableAllowAccessModification(
+        ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart) {
       this.allowAccessModification = true;
     }
 
     @Override
-    public void setIgnoreWarnings(boolean ignoreWarnings) {
-      this.ignoreWarnings = ignoreWarnings;
+    public void setIgnoreWarnings(
+        ProguardConfigurationSourceParser parser, TextPosition positionStart) {
+      this.ignoreWarnings = true;
     }
 
     @Override
-    public void disableOptimization(Origin origin, Position position) {
+    public void disableOptimization(ProguardConfigurationSourceParser parser, Position position) {
       this.optimizing = false;
     }
 
     @Override
-    public void disableObfuscation(Origin origin, Position position) {
+    public void disableObfuscation(ProguardConfigurationSourceParser parser, Position position) {
       this.obfuscating = false;
     }
 
     @Override
-    public void disableShrinking(Origin origin, Position position) {
+    public void disableShrinking(ProguardConfigurationSourceParser parser, Position position) {
       this.shrinking = false;
     }
 
@@ -135,10 +185,6 @@
       return this;
     }
 
-    boolean isAccessModificationEnabled() {
-      return allowAccessModification;
-    }
-
     boolean isObfuscating() {
       return obfuscating;
     }
@@ -157,79 +203,114 @@
     }
 
     @Override
-    public void enablePrintConfiguration(Origin origin, Position position) {
+    public void enablePrintConfiguration(
+        Path printConfigurationFile,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       this.printConfiguration = true;
+      this.printConfigurationFile = printConfigurationFile;
     }
 
     @Override
-    public void setPrintConfigurationFile(Path file) {
-      assert printConfiguration;
-      this.printConfigurationFile = file;
-    }
-
-    @Override
-    public void setPrintUsage(boolean printUsage) {
-      this.printUsage = printUsage;
-    }
-
-    @Override
-    public void setPrintUsageFile(Path printUsageFile) {
+    public void enablePrintUsage(
+        Path printUsageFile,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
+      this.printUsage = true;
       this.printUsageFile = printUsageFile;
     }
 
     @Override
-    public void enablePrintMapping(Origin origin, Position position) {
+    public void enablePrintMapping(
+        Path printMappingFile,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       this.printMapping = true;
+      this.printMappingFile = printMappingFile;
     }
 
     @Override
-    public void setPrintMappingFile(Path file) {
-      assert printMapping;
-      this.printMappingFile = file;
-    }
-
-    @Override
-    public void setApplyMappingFile(Path file, Origin origin, Position position) {
-      this.applyMappingFile = file;
-    }
-
-    public boolean hasApplyMappingFile() {
-      return applyMappingFile != null;
+    public void setApplyMappingFile(
+        Path applyMappingFile,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
+      this.applyMappingFile = applyMappingFile;
     }
 
     @Override
     public void setRenameSourceFileAttribute(
-        String renameSourceFileAttribute, Origin origin, Position position) {
+        String renameSourceFileAttribute,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       this.renameSourceFileAttribute = renameSourceFileAttribute;
     }
 
     @Override
     public void addKeepAttributePatterns(
         List<String> keepAttributePatterns,
-        Origin origin,
         ProguardConfigurationSourceParser parser,
         Position position,
         TextPosition positionStart) {
       this.keepAttributePatterns.addAll(keepAttributePatterns);
     }
 
+    @Override
+    public void addKeepKotlinMetadata(
+        ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart) {
+      Origin origin = parser.getOrigin();
+      String source = "-keepkotlinmetadata";
+      ProguardKeepRule keepKotlinMetadata =
+          ProguardKeepRuleUtils.keepClassAndMembersRule(
+              origin, positionStart, dexItemFactory.kotlinMetadataType, source);
+      ProguardKeepRule keepKotlinJvmNameAnnotation =
+          ProguardKeepRuleUtils.keepClassAndMembersRule(
+              origin, positionStart, dexItemFactory.kotlinJvmNameType, source);
+      // Mark the rules as used to ensure we do not report any information messages if the class
+      // is not present.
+      keepKotlinMetadata.markAsUsed();
+      keepKotlinJvmNameAnnotation.markAsUsed();
+      addRule(keepKotlinMetadata, parser, positionStart);
+      addRule(keepKotlinJvmNameAnnotation, parser, positionStart);
+      addKeepAttributePatterns(
+          Collections.singletonList(RUNTIME_VISIBLE_ANNOTATIONS), parser, position, positionStart);
+      addKeepAttributePatterns(
+          Collections.singletonList(RUNTIME_INVISIBLE_ANNOTATIONS),
+          parser,
+          position,
+          positionStart);
+    }
+
     public Builder addKeepAttributePatterns(List<String> keepAttributePatterns) {
       this.keepAttributePatterns.addAll(keepAttributePatterns);
       return this;
     }
 
     @Override
-    public void addRule(ProguardConfigurationRule rule) {
+    public void addRule(
+        ProguardConfigurationRule rule,
+        ProguardConfigurationSourceParser parser,
+        TextPosition positionStart) {
       this.rules.add(rule);
     }
 
     @Override
-    public void addKeepPackageNamesPattern(ProguardClassNameList pattern) {
+    public void addKeepPackageNamesPattern(
+        ProguardClassNameList pattern,
+        ProguardConfigurationSourceParser parser,
+        TextPosition positionStart) {
       keepPackageNamesPatterns.addPattern(pattern);
     }
 
     @Override
-    public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
+    public void joinMaxRemovedAndroidLogLevel(
+        int maxRemovedAndroidLogLevel,
+        ProguardConfigurationSourceParser parser,
+        TextPosition positionStart) {
       assert maxRemovedAndroidLogLevel >= MaximumRemovedAndroidLogLevelRule.NONE;
       if (this.maxRemovedAndroidLogLevel == MaximumRemovedAndroidLogLevelRule.NOT_SET) {
         this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
@@ -251,91 +332,100 @@
     }
 
     @Override
-    public void setPackagePrefix(String packagePrefix) {
-      this.packagePrefix = packagePrefix;
-    }
-
-    @Override
-    public void setFlattenPackagePrefix(String packagePrefix) {
-      this.packagePrefix = packagePrefix;
-    }
-
-    @Override
-    public void enableFlattenPackageHierarchy(Origin origin, Position position) {
+    public void enableFlattenPackageHierarchy(
+        String packagePrefix,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       packageObfuscationMode = PackageObfuscationMode.FLATTEN;
+      this.packagePrefix = packagePrefix;
     }
 
     @Override
-    public void enableRepackageClasses(Origin origin, Position position) {
+    public void enableRepackageClasses(
+        String packagePrefix,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       packageObfuscationMode = PackageObfuscationMode.REPACKAGE;
+      this.packagePrefix = packagePrefix;
     }
 
     @Override
-    public void addDontWarnPattern(ProguardClassNameList pattern) {
+    public void addDontWarnPattern(
+        ProguardClassNameList pattern,
+        ProguardConfigurationSourceParser parser,
+        TextPosition positionStart) {
       dontWarnPatterns.addPattern(pattern);
     }
 
     @Override
-    public void addDontNotePattern(ProguardClassNameList pattern) {
+    public void addDontNotePattern(
+        ProguardClassNameList pattern,
+        ProguardConfigurationSourceParser parser,
+        TextPosition positionStart) {
       dontNotePatterns.addPattern(pattern);
     }
 
     @Override
-    public void setSeedFile(Path seedFile) {
-      this.seedFile = seedFile;
-    }
-
-    @Override
-    public void setPrintSeeds(boolean printSeeds, Origin origin, Position position) {
-      this.printSeeds = printSeeds;
+    public void enablePrintSeeds(
+        Path printSeedsFile,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
+      this.printSeeds = true;
+      this.printSeedsFile = printSeedsFile;
     }
 
     @Override
     public void setObfuscationDictionary(
-        Path obfuscationDictionary, Origin origin, Position position) {
+        Path obfuscationDictionary,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       this.obfuscationDictionary = obfuscationDictionary;
     }
 
     @Override
     public void setClassObfuscationDictionary(
-        Path classObfuscationDictionary, Origin origin, Position position) {
+        Path classObfuscationDictionary,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       this.classObfuscationDictionary = classObfuscationDictionary;
     }
 
     @Override
     public void setPackageObfuscationDictionary(
-        Path packageObfuscationDictionary, Origin origin, Position position) {
+        Path packageObfuscationDictionary,
+        ProguardConfigurationSourceParser parser,
+        Position position,
+        TextPosition positionStart) {
       this.packageObfuscationDictionary = packageObfuscationDictionary;
     }
 
     @Override
     public void setKeepParameterNames(
-        boolean keepParameterNames, Origin optionOrigin, Position optionPosition) {
+        ProguardConfigurationSourceParser optionOrigin,
+        Position optionPosition,
+        TextPosition positionStart) {
       assert optionOrigin != null || !keepParameterNames;
-      this.keepParameterNames = keepParameterNames;
-      this.keepParameterNamesOptionOrigin = optionOrigin;
-      this.keepParameterNamesOptionPosition = optionPosition;
-    }
-
-    boolean isKeepParameterNames() {
-      return keepParameterNames;
-    }
-
-    Origin getKeepParameterNamesOptionOrigin() {
-      return keepParameterNamesOptionOrigin;
-    }
-
-    Position getKeepParameterNamesOptionPosition() {
-      return keepParameterNamesOptionPosition;
+      this.keepParameterNames = true;
     }
 
     @Override
-    public void addAdaptClassStringsPattern(ProguardClassNameList pattern) {
+    public void addAdaptClassStringsPattern(
+        ProguardClassNameList pattern,
+        ProguardConfigurationSourceParser parser,
+        TextPosition positionStart) {
       adaptClassStrings.addPattern(pattern);
     }
 
     @Override
-    public void addAdaptResourceFilenames(ProguardPathList pattern) {
+    public void addAdaptResourceFilenames(
+        ProguardPathList pattern,
+        ProguardConfigurationSourceParser parser,
+        TextPosition positionStart) {
       adaptResourceFilenames.addPattern(pattern);
     }
 
@@ -346,18 +436,19 @@
     }
 
     @Override
-    public void addAdaptResourceFileContents(ProguardPathList pattern) {
+    public void addAdaptResourceFileContents(
+        ProguardPathList pattern,
+        ProguardConfigurationSourceParser parser,
+        TextPosition positionStart) {
       adaptResourceFileContents.addPattern(pattern);
     }
 
     @Override
-    public void enableKeepDirectories() {
-      keepDirectories.enable();
-    }
-
-    @Override
-    public void addKeepDirectories(ProguardPathList pattern) {
-      keepDirectories.addPattern(pattern);
+    public void enableKeepDirectories(
+        ProguardPathList keepDirectoryPatterns,
+        ProguardConfigurationSourceParser parser,
+        TextPosition positionStart) {
+      keepDirectories.enable().addPattern(keepDirectoryPatterns);
     }
 
     public boolean isForceProguardCompatibility() {
@@ -370,7 +461,8 @@
     }
 
     @Override
-    public void enableProtoShrinking() {
+    public void enableProtoShrinking(
+        ProguardConfigurationSourceParser parser, TextPosition positionStart) {
       protoShrinking = true;
     }
 
@@ -383,7 +475,7 @@
       }
       ProguardConfiguration configuration =
           new ProguardConfiguration(
-              String.join(System.lineSeparator(), parsedConfiguration),
+              parsedConfiguration.toString(),
               dexItemFactory,
               injars,
               libraryJars,
@@ -408,7 +500,7 @@
               dontNotePatterns.build(),
               rules,
               printSeeds,
-              seedFile,
+              printSeedsFile,
               DictionaryReader.readAllNames(obfuscationDictionary, reporter),
               DictionaryReader.readAllNames(classObfuscationDictionary, reporter),
               DictionaryReader.readAllNames(packageObfuscationDictionary, reporter),
@@ -471,6 +563,8 @@
   private final ProguardPathFilter keepDirectories;
   private final boolean protoShrinking;
   private final int maxRemovedAndroidLogLevel;
+  private final boolean hasWhyAreYouNotInliningRule;
+  private final boolean hasWhyAreYouNotObfuscatingRule;
 
   private ProguardConfiguration(
       String parsedConfiguration,
@@ -545,6 +639,10 @@
     this.keepDirectories = keepDirectories;
     this.protoShrinking = protoShrinking;
     this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+    this.hasWhyAreYouNotInliningRule =
+        Iterables.any(rules, rule -> rule instanceof WhyAreYouNotInliningRule);
+    this.hasWhyAreYouNotObfuscatingRule =
+        Iterables.any(rules, rule -> rule instanceof WhyAreYouNotObfuscatingRule);
   }
 
   /**
@@ -716,6 +814,14 @@
     return Iterables.any(rules, ProguardConfigurationRule::isMaximumRemovedAndroidLogLevelRule);
   }
 
+  public boolean hasWhyAreYouNotInliningRule() {
+    return hasWhyAreYouNotInliningRule;
+  }
+
+  public boolean hasWhyAreYouNotObfuscatingRule() {
+    return hasWhyAreYouNotObfuscatingRule;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index dddd397..61355d7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.shaking.ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS;
-import static com.android.tools.r8.shaking.ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS;
 import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
 
 import com.android.tools.r8.InputDependencyGraphConsumer;
@@ -39,8 +37,11 @@
 import java.nio.CharBuffer;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.List;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
@@ -87,7 +88,7 @@
           "dontusemixedcaseclassnames");
 
   private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS =
-      ImmutableList.of("isclassnamestring", "whyarenotsimple");
+      ImmutableList.of("checkenumstringsdiscarded", "isclassnamestring", "whyarenotsimple");
 
   private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList.of(
       // TODO(b/37137994): -outjars should be reported as errors, not just as warnings!
@@ -135,7 +136,6 @@
         reporter,
         ProguardConfigurationParserOptions.builder()
             .setEnableLegacyFullModeForKeepRules(false)
-            .setEnableExperimentalCheckEnumUnboxed(false)
             .setEnableTestingOptions(false)
             .build(),
         null,
@@ -224,22 +224,45 @@
     private final String name;
     private final String contents;
     private int position = 0;
-    private int positionAfterInclude = 0;
     private int line = 1;
     private int lineStartPosition = 0;
     private Path baseDirectory;
     private final Origin origin;
 
+    private final Deque<IncludeWorkItem> pendingIncludes = new ArrayDeque<>();
+
     ProguardConfigurationSourceParser(ProguardConfigurationSource source) throws IOException {
       // Strip any leading BOM here so it is not included in the text position.
-      contents = StringUtils.stripLeadingBOM(source.get());
       baseDirectory = source.getBaseDirectory();
       name = source.getName();
       this.origin = source.getOrigin();
+      String sourceWithPossibleLeadingBOM = source.get();
+      if (StringUtils.hasLeadingBOM(sourceWithPossibleLeadingBOM)) {
+        contents = StringUtils.stripLeadingBOM(sourceWithPossibleLeadingBOM);
+        configurationConsumer.addLeadingBOM();
+      } else {
+        contents = sourceWithPossibleLeadingBOM;
+      }
+    }
+
+    public String getContentAfter(int start) {
+      return getContentInRange(start, contents.length());
+    }
+
+    public String getContentInRange(int start, int end) {
+      return contents.substring(start, end);
     }
 
     public String getContentSince(TextPosition start) {
-      return contents.substring(start.getOffsetAsInt(), position);
+      return getContentInRange(start.getOffsetAsInt(), position);
+    }
+
+    public Origin getOrigin() {
+      return origin;
+    }
+
+    public Collection<IncludeWorkItem> getPendingIncludes() {
+      return pendingIncludes;
     }
 
     public void parse() throws ProguardRuleParserException {
@@ -249,14 +272,11 @@
           configurationConsumer.addWhitespace(this, whitespaceStart);
         }
       } while (parseOption());
-      // This may be unknown, but we want to always ensure that we don't attribute lines to the
-      // wrong configuration.
-      configurationConsumer.addParsedConfiguration(
-          "# The proguard configuration file for the following section is " + origin.toString());
-
-      // Collect the parsed configuration.
-      configurationConsumer.addParsedConfiguration(contents.substring(positionAfterInclude));
-      configurationConsumer.addParsedConfiguration("# End of content from " + origin);
+      configurationConsumer.addParsedConfiguration(this);
+      while (!pendingIncludes.isEmpty()) {
+        IncludeWorkItem includeWorkItem = pendingIncludes.removeFirst();
+        parseInclude(includeWorkItem.includePath, includeWorkItem.includePositionStart);
+      }
       reporter.failIfPendingErrors();
     }
 
@@ -264,74 +284,48 @@
       if (eof()) {
         return false;
       }
-      if (acceptArobaseInclude()) {
+      TextPosition optionStart = getPosition();
+      if (acceptArobaseInclude(optionStart)) {
         return true;
       }
-      TextPosition optionStart = getPosition();
       expectChar('-');
       if (parseIgnoredOption(optionStart)
           || parseIgnoredOptionAndWarn(optionStart)
-          || parseExperimentalOption(optionStart)
           || parseTestingOption(optionStart)
           || parseUnsupportedOptionAndErr(optionStart)) {
         // Intentionally left empty.
       } else if (acceptString("keepkotlinmetadata")) {
-        String source = "-keepkotlinmetadata";
-        ProguardKeepRule keepKotlinMetadata =
-            ProguardKeepRuleUtils.keepClassAndMembersRule(
-                origin, optionStart, dexItemFactory.kotlinMetadataType, source);
-        ProguardKeepRule keepKotlinJvmNameAnnotation =
-            ProguardKeepRuleUtils.keepClassAndMembersRule(
-                origin, optionStart, dexItemFactory.kotlinJvmNameType, source);
-        // Mark the rules as used to ensure we do not report any information messages if the class
-        // is not present.
-        keepKotlinMetadata.markAsUsed();
-        keepKotlinJvmNameAnnotation.markAsUsed();
-        configurationConsumer.addRule(keepKotlinMetadata);
-        configurationConsumer.addRule(keepKotlinJvmNameAnnotation);
-        configurationConsumer.addKeepAttributePatterns(
-            Collections.singletonList(RUNTIME_VISIBLE_ANNOTATIONS),
-            origin,
-            this,
-            getPosition(optionStart),
-            optionStart);
-        configurationConsumer.addKeepAttributePatterns(
-            Collections.singletonList(RUNTIME_INVISIBLE_ANNOTATIONS),
-            origin,
-            this,
-            getPosition(optionStart),
-            optionStart);
+        configurationConsumer.addKeepKotlinMetadata(this, getPosition(optionStart), optionStart);
       } else if (acceptString("renamesourcefileattribute")) {
         skipWhitespace();
         String renameSourceFileAttribute =
             isOptionalArgumentGiven() ? acceptQuotedOrUnquotedString() : "";
         configurationConsumer.setRenameSourceFileAttribute(
-            renameSourceFileAttribute, origin, getPosition(optionStart));
+            renameSourceFileAttribute, this, getPosition(optionStart), optionStart);
       } else if (acceptString("keepattributes")) {
         parseKeepAttributes(optionStart);
       } else if (acceptString("keeppackagenames")) {
-        parseClassFilter(configurationConsumer::addKeepPackageNamesPattern);
+        ProguardClassNameList keepPackageNamePatterns = parseOptionalClassFilter();
+        configurationConsumer.addKeepPackageNamesPattern(
+            keepPackageNamePatterns, this, optionStart);
       } else if (acceptString("keepparameternames")) {
-        configurationConsumer.setKeepParameterNames(true, origin, getPosition(optionStart));
+        configurationConsumer.setKeepParameterNames(this, getPosition(optionStart), optionStart);
       } else if (acceptString("checkdiscard")) {
         ProguardCheckDiscardRule rule =
             parseRuleWithClassSpec(optionStart, ProguardCheckDiscardRule.builder());
-        configurationConsumer.addRule(rule);
-      } else if (acceptString("checkenumstringsdiscarded")) {
-        // Not supported, ignore.
-        parseRuleWithClassSpec(optionStart, ProguardCheckDiscardRule.builder());
+        configurationConsumer.addRule(rule, this, optionStart);
       } else if (acceptString("keepdirectories")) {
-        configurationConsumer.enableKeepDirectories();
-        parsePathFilter(configurationConsumer::addKeepDirectories);
+        ProguardPathList keepDirectoryPatterns = parseOptionalPathFilter();
+        configurationConsumer.enableKeepDirectories(keepDirectoryPatterns, this, optionStart);
       } else if (acceptString("keep")) {
         ProguardKeepRule rule = parseKeepRule(optionStart);
-        configurationConsumer.addRule(rule);
+        configurationConsumer.addRule(rule, this, optionStart);
       } else if (acceptString("whyareyoukeeping")) {
-        ProguardWhyAreYouKeepingRule rule =
-            parseRuleWithClassSpec(optionStart, ProguardWhyAreYouKeepingRule.builder());
-        configurationConsumer.addRule(rule);
+        WhyAreYouKeepingRule rule =
+            parseRuleWithClassSpec(optionStart, WhyAreYouKeepingRule.builder());
+        configurationConsumer.addRule(rule, this, optionStart);
       } else if (acceptString("dontoptimize")) {
-        configurationConsumer.disableOptimization(origin, getPosition(optionStart));
+        configurationConsumer.disableOptimization(this, getPosition(optionStart));
       } else if (acceptString("optimizationpasses")) {
         skipWhitespace();
         Integer expectedOptimizationPasses = acceptInteger();
@@ -339,42 +333,43 @@
           throw reporter.fatalError(new StringDiagnostic(
               "Missing n of \"-optimizationpasses n\"", origin, getPosition(optionStart)));
         }
+        configurationConsumer.addIgnoredOption("optimizationpasses", this, optionStart);
         infoIgnoringOptions("optimizationpasses", optionStart);
       } else if (acceptString("dontobfuscate")) {
-        configurationConsumer.disableObfuscation(origin, getPosition(optionStart));
+        configurationConsumer.disableObfuscation(this, getPosition(optionStart));
       } else if (acceptString("dontshrink")) {
-        configurationConsumer.disableShrinking(origin, getPosition(optionStart));
+        configurationConsumer.disableShrinking(this, getPosition(optionStart));
       } else if (acceptString("printusage")) {
-        configurationConsumer.setPrintUsage(true);
         skipWhitespace();
-        if (isOptionalArgumentGiven()) {
-          configurationConsumer.setPrintUsageFile(parseFileName(false));
-        }
+        configurationConsumer.enablePrintUsage(
+            parseOptionalFileName(), this, getPosition(optionStart), optionStart);
       } else if (acceptString("shrinkunusedprotofields")) {
-        configurationConsumer.enableProtoShrinking();
+        configurationConsumer.enableProtoShrinking(this, optionStart);
       } else if (acceptString("ignorewarnings")) {
-        configurationConsumer.setIgnoreWarnings(true);
+        configurationConsumer.setIgnoreWarnings(this, optionStart);
       } else if (acceptString("dontwarn")) {
-        parseClassFilter(configurationConsumer::addDontWarnPattern);
+        ProguardClassNameList dontWarnPattern = parseOptionalClassFilter();
+        configurationConsumer.addDontWarnPattern(dontWarnPattern, this, optionStart);
       } else if (acceptString("dontnote")) {
-        parseClassFilter(configurationConsumer::addDontNotePattern);
+        ProguardClassNameList dontNotePattern = parseOptionalClassFilter();
+        configurationConsumer.addDontNotePattern(dontNotePattern, this, optionStart);
       } else if (acceptString(REPACKAGE_CLASSES)) {
         if (configurationConsumer.getPackageObfuscationMode() == PackageObfuscationMode.FLATTEN) {
           warnOverridingOptions(REPACKAGE_CLASSES, FLATTEN_PACKAGE_HIERARCHY, optionStart);
         }
         skipWhitespace();
         char quote = acceptQuoteIfPresent();
+        String packagePrefix;
         if (isQuote(quote)) {
-          configurationConsumer.setPackagePrefix(parsePackageNameOrEmptyString());
+          packagePrefix = parsePackageNameOrEmptyString();
           expectClosingQuote(quote);
+        } else if (hasNextChar('-')) {
+          packagePrefix = "";
         } else {
-          if (hasNextChar('-')) {
-            configurationConsumer.setPackagePrefix("");
-          } else {
-            configurationConsumer.setPackagePrefix(parsePackageNameOrEmptyString());
-          }
+          packagePrefix = parsePackageNameOrEmptyString();
         }
-        configurationConsumer.enableRepackageClasses(origin, getPosition(optionStart));
+        configurationConsumer.enableRepackageClasses(
+            packagePrefix, this, getPosition(optionStart), optionStart);
       } else if (acceptString(FLATTEN_PACKAGE_HIERARCHY)) {
         if (configurationConsumer.getPackageObfuscationMode() == PackageObfuscationMode.REPACKAGE) {
           warnOverridingOptions(REPACKAGE_CLASSES, FLATTEN_PACKAGE_HIERARCHY, optionStart);
@@ -385,108 +380,119 @@
         } else {
           skipWhitespace();
           char quote = acceptQuoteIfPresent();
+          String packagePrefix;
           if (isQuote(quote)) {
-            configurationConsumer.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
+            packagePrefix = parsePackageNameOrEmptyString();
             expectClosingQuote(quote);
+          } else if (hasNextChar('-')) {
+            packagePrefix = "";
           } else {
-            if (hasNextChar('-')) {
-              configurationConsumer.setFlattenPackagePrefix("");
-            } else {
-              configurationConsumer.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
-            }
+            packagePrefix = parsePackageNameOrEmptyString();
           }
-          configurationConsumer.enableFlattenPackageHierarchy(origin, getPosition(optionStart));
+          configurationConsumer.enableFlattenPackageHierarchy(
+              packagePrefix, this, getPosition(optionStart), optionStart);
         }
       } else if (acceptString("allowaccessmodification")) {
-        configurationConsumer.enableAllowAccessModification(origin, getPosition(optionStart));
+        configurationConsumer.enableAllowAccessModification(
+            this, getPosition(optionStart), optionStart);
       } else if (acceptString("printconfiguration")) {
-        configurationConsumer.enablePrintConfiguration(origin, getPosition(optionStart));
         skipWhitespace();
-        if (isOptionalArgumentGiven()) {
-          configurationConsumer.setPrintConfigurationFile(parseFileName(false));
-        }
+        configurationConsumer.enablePrintConfiguration(
+            parseOptionalFileName(), this, getPosition(optionStart), optionStart);
       } else if (acceptString("printmapping")) {
-        configurationConsumer.enablePrintMapping(origin, getPosition(optionStart));
         skipWhitespace();
-        if (isOptionalArgumentGiven()) {
-          configurationConsumer.setPrintMappingFile(parseFileName(false));
-        }
+        configurationConsumer.enablePrintMapping(
+            parseOptionalFileName(), this, getPosition(optionStart), optionStart);
       } else if (acceptString("applymapping")) {
+        Path applyMappingFile =
+            parseFileInputDependency(inputDependencyConsumer::acceptProguardApplyMapping);
         configurationConsumer.setApplyMappingFile(
-            parseFileInputDependency(inputDependencyConsumer::acceptProguardApplyMapping),
-            origin,
-            getPosition(optionStart));
+            applyMappingFile, this, getPosition(optionStart), optionStart);
       } else if (acceptString("assumenosideeffects")) {
         ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule(optionStart);
-        configurationConsumer.addRule(rule);
+        configurationConsumer.addRule(rule, this, optionStart);
       } else if (acceptString("assumevalues")) {
         ProguardAssumeValuesRule rule = parseAssumeValuesRule(optionStart);
-        configurationConsumer.addRule(rule);
+        configurationConsumer.addRule(rule, this, optionStart);
       } else if (acceptString("include")) {
-        // Collect the parsed configuration until the include.
-        configurationConsumer.addParsedConfiguration(
-            contents.substring(positionAfterInclude, position - ("include".length() + 1)));
         skipWhitespace();
-        parseInclude();
-        positionAfterInclude = position;
+        enqueueInclude(optionStart);
       } else if (acceptString("basedirectory")) {
         skipWhitespace();
-        baseDirectory = parseFileName(false);
+        baseDirectory = parseFileName();
+        configurationConsumer.addBaseDirectory(baseDirectory, this, optionStart);
       } else if (acceptString("injars")) {
         configurationConsumer.addInjars(
             parseClassPath(inputDependencyConsumer::acceptProguardInJars),
-            origin,
-            getPosition(optionStart));
+            this,
+            getPosition(optionStart),
+            optionStart);
       } else if (acceptString("libraryjars")) {
         configurationConsumer.addLibraryJars(
             parseClassPath(inputDependencyConsumer::acceptProguardLibraryJars),
-            origin,
-            getPosition(optionStart));
+            this,
+            getPosition(optionStart),
+            optionStart);
       } else if (acceptString("printseeds")) {
-        configurationConsumer.setPrintSeeds(true, origin, getPosition(optionStart));
         skipWhitespace();
-        if (isOptionalArgumentGiven()) {
-          configurationConsumer.setSeedFile(parseFileName(false));
-        }
+        configurationConsumer.enablePrintSeeds(
+            parseOptionalFileName(), this, getPosition(optionStart), optionStart);
       } else if (acceptString("obfuscationdictionary")) {
+        Path obfuscationDictionary =
+            parseFileInputDependency(inputDependencyConsumer::acceptProguardObfuscationDictionary);
         configurationConsumer.setObfuscationDictionary(
-            parseFileInputDependency(inputDependencyConsumer::acceptProguardObfuscationDictionary),
-            origin,
-            getPosition(optionStart));
+            obfuscationDictionary, this, getPosition(optionStart), optionStart);
       } else if (acceptString("classobfuscationdictionary")) {
+        Path classObfuscationDictionary =
+            parseFileInputDependency(
+                inputDependencyConsumer::acceptProguardClassObfuscationDictionary);
         configurationConsumer.setClassObfuscationDictionary(
-            parseFileInputDependency(
-                inputDependencyConsumer::acceptProguardClassObfuscationDictionary),
-            origin,
-            getPosition(optionStart));
+            classObfuscationDictionary, this, getPosition(optionStart), optionStart);
       } else if (acceptString("packageobfuscationdictionary")) {
-        configurationConsumer.setPackageObfuscationDictionary(
+        Path packageObfuscationDictionary =
             parseFileInputDependency(
-                inputDependencyConsumer::acceptProguardPackageObfuscationDictionary),
-            origin,
-            getPosition(optionStart));
+                inputDependencyConsumer::acceptProguardPackageObfuscationDictionary);
+        configurationConsumer.setPackageObfuscationDictionary(
+            packageObfuscationDictionary, this, getPosition(optionStart), optionStart);
       } else if (acceptString("alwaysinline")) {
         InlineRule rule =
             parseRuleWithClassSpec(
                 optionStart, InlineRule.builder().setType(InlineRuleType.ALWAYS));
-        configurationConsumer.addRule(rule);
+        configurationConsumer.addRule(rule, this, optionStart);
       } else if (acceptString("adaptclassstrings")) {
-        parseClassFilter(configurationConsumer::addAdaptClassStringsPattern);
+        ProguardClassNameList adaptClassStringsPattern = parseOptionalClassFilter();
+        configurationConsumer.addAdaptClassStringsPattern(
+            adaptClassStringsPattern, this, optionStart);
       } else if (acceptString("adaptresourcefilenames")) {
-        parsePathFilter(configurationConsumer::addAdaptResourceFilenames);
+        ProguardPathList pattern = parseOptionalPathFilter();
+        configurationConsumer.addAdaptResourceFilenames(pattern, this, optionStart);
       } else if (acceptString("adaptresourcefilecontents")) {
-        parsePathFilter(configurationConsumer::addAdaptResourceFileContents);
+        ProguardPathList pattern = parseOptionalPathFilter();
+        configurationConsumer.addAdaptResourceFileContents(pattern, this, optionStart);
       } else if (acceptString("identifiernamestring")) {
         configurationConsumer.addRule(
-            parseRuleWithClassSpec(optionStart, ProguardIdentifierNameStringRule.builder()));
+            parseRuleWithClassSpec(optionStart, ProguardIdentifierNameStringRule.builder()),
+            this,
+            optionStart);
       } else if (acceptString("if")) {
-        configurationConsumer.addRule(parseIfRule(optionStart));
+        configurationConsumer.addRule(parseIfRule(optionStart), this, optionStart);
+      } else if (acceptString(CheckEnumUnboxedRule.RULE_NAME)) {
+        configurationConsumer.addRule(parseCheckEnumUnboxedRule(optionStart), this, optionStart);
+        return true;
       } else if (acceptString(ConvertCheckNotNullRule.RULE_NAME)) {
-        configurationConsumer.addRule(parseConvertCheckNotNullRule(optionStart));
+        configurationConsumer.addRule(parseConvertCheckNotNullRule(optionStart), this, optionStart);
+        return true;
+      } else if (acceptString(WhyAreYouNotObfuscatingRule.RULE_NAME)) {
+        configurationConsumer.addRule(
+            parseRuleWithClassSpec(optionStart, WhyAreYouNotObfuscatingRule.builder()),
+            this,
+            optionStart);
         return true;
       } else if (acceptString(WhyAreYouNotInliningRule.RULE_NAME)) {
         configurationConsumer.addRule(
-            parseRuleWithClassSpec(optionStart, WhyAreYouNotInliningRule.builder()));
+            parseRuleWithClassSpec(optionStart, WhyAreYouNotInliningRule.builder()),
+            this,
+            optionStart);
         return true;
       } else if (parseMaximumRemovedAndroidLogLevelRule(optionStart)) {
         return true;
@@ -503,171 +509,83 @@
       return true;
     }
 
-    private boolean parseExperimentalOption(TextPosition optionStart)
-        throws ProguardRuleParserException {
-      if (acceptString(CheckEnumUnboxedRule.RULE_NAME)) {
-        CheckEnumUnboxedRule checkEnumUnboxedRule = parseCheckEnumUnboxedRule(optionStart);
-        if (options.isExperimentalCheckEnumUnboxedEnabled()) {
-          configurationConsumer.addRule(checkEnumUnboxedRule);
-        }
-        return true;
-      }
-      return false;
-    }
-
     private boolean parseTestingOption(TextPosition optionStart)
         throws ProguardRuleParserException {
-      if (options.isTestingOptionsEnabled()) {
-        if (acceptString("assumemayhavesideeffects")) {
-          ProguardAssumeMayHaveSideEffectsRule rule =
-              parseAssumeMayHaveSideEffectsRule(optionStart);
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(KeepConstantArgumentRule.RULE_NAME)) {
-          KeepConstantArgumentRule rule =
-              parseRuleWithClassSpec(optionStart, KeepConstantArgumentRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(KeepUnusedArgumentRule.RULE_NAME)) {
-          KeepUnusedArgumentRule rule =
-              parseRuleWithClassSpec(optionStart, KeepUnusedArgumentRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(KeepUnusedReturnValueRule.RULE_NAME)) {
-          KeepUnusedReturnValueRule rule =
-              parseRuleWithClassSpec(optionStart, KeepUnusedReturnValueRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString("alwaysclassinline")) {
-          ClassInlineRule rule =
-              parseRuleWithClassSpec(
-                  optionStart, ClassInlineRule.builder().setType(ClassInlineRule.Type.ALWAYS));
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString("neverclassinline")) {
-          ClassInlineRule rule =
-              parseRuleWithClassSpec(
-                  optionStart, ClassInlineRule.builder().setType(ClassInlineRule.Type.NEVER));
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString("neverinline")) {
-          InlineRule rule =
-              parseRuleWithClassSpec(
-                  optionStart, InlineRule.builder().setType(InlineRuleType.NEVER));
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString("neversinglecallerinline")) {
-          InlineRule rule =
-              parseRuleWithClassSpec(
-                  optionStart, InlineRule.builder().setType(InlineRuleType.NEVER_SINGLE_CALLER));
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoAccessModificationRule.RULE_NAME)) {
-          NoAccessModificationRule rule =
-              parseRuleWithClassSpec(optionStart, NoAccessModificationRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoFieldTypeStrengtheningRule.RULE_NAME)) {
-          NoFieldTypeStrengtheningRule rule =
-              parseRuleWithClassSpec(optionStart, NoFieldTypeStrengtheningRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoUnusedInterfaceRemovalRule.RULE_NAME)) {
-          NoUnusedInterfaceRemovalRule rule =
-              parseRuleWithClassSpec(optionStart, NoUnusedInterfaceRemovalRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoVerticalClassMergingRule.RULE_NAME)) {
-          NoVerticalClassMergingRule rule =
-              parseRuleWithClassSpec(optionStart, NoVerticalClassMergingRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoHorizontalClassMergingRule.RULE_NAME)) {
-          NoHorizontalClassMergingRule rule =
-              parseRuleWithClassSpec(optionStart, NoHorizontalClassMergingRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoMethodStaticizingRule.RULE_NAME)) {
-          NoMethodStaticizingRule rule =
-              parseRuleWithClassSpec(optionStart, NoMethodStaticizingRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoParameterReorderingRule.RULE_NAME)) {
-          NoParameterReorderingRule rule =
-              parseRuleWithClassSpec(optionStart, NoParameterReorderingRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoParameterTypeStrengtheningRule.RULE_NAME)) {
-          NoParameterTypeStrengtheningRule rule =
-              parseRuleWithClassSpec(optionStart, NoParameterTypeStrengtheningRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoRedundantFieldLoadEliminationRule.RULE_NAME)) {
-          NoRedundantFieldLoadEliminationRule rule =
-              parseRuleWithClassSpec(optionStart, NoRedundantFieldLoadEliminationRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString(NoReturnTypeStrengtheningRule.RULE_NAME)) {
-          NoReturnTypeStrengtheningRule rule =
-              parseRuleWithClassSpec(optionStart, NoReturnTypeStrengtheningRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString("neverpropagatevalue")) {
-          NoValuePropagationRule rule =
-              parseRuleWithClassSpec(optionStart, NoValuePropagationRule.builder());
-          configurationConsumer.addRule(rule);
-          return true;
-        }
-        if (acceptString("neverreprocessclassinitializer")) {
-          configurationConsumer.addRule(
-              parseRuleWithClassSpec(
-                  optionStart,
-                  ReprocessClassInitializerRule.builder()
-                      .setType(ReprocessClassInitializerRule.Type.NEVER)));
-          return true;
-        }
-        if (acceptString("neverreprocessmethod")) {
-          configurationConsumer.addRule(
-              parseRuleWithClassSpec(
-                  optionStart,
-                  ReprocessMethodRule.builder().setType(ReprocessMethodRule.Type.NEVER)));
-          return true;
-        }
-        if (acceptString("reprocessclassinitializer")) {
-          configurationConsumer.addRule(
-              parseRuleWithClassSpec(
-                  optionStart,
-                  ReprocessClassInitializerRule.builder()
-                      .setType(ReprocessClassInitializerRule.Type.ALWAYS)));
-          return true;
-        }
-        if (acceptString("reprocessmethod")) {
-          configurationConsumer.addRule(
-              parseRuleWithClassSpec(
-                  optionStart,
-                  ReprocessMethodRule.builder().setType(ReprocessMethodRule.Type.ALWAYS)));
-          return true;
-        }
+      if (!options.isTestingOptionsEnabled()) {
+        return false;
       }
-      return false;
+      ProguardConfigurationRule rule;
+      if (acceptString("assumemayhavesideeffects")) {
+        rule = parseAssumeMayHaveSideEffectsRule(optionStart);
+      } else if (acceptString(KeepConstantArgumentRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, KeepConstantArgumentRule.builder());
+      } else if (acceptString(KeepUnusedArgumentRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, KeepUnusedArgumentRule.builder());
+      } else if (acceptString(KeepUnusedReturnValueRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, KeepUnusedReturnValueRule.builder());
+      } else if (acceptString("alwaysclassinline")) {
+        rule =
+            parseRuleWithClassSpec(
+                optionStart, ClassInlineRule.builder().setType(ClassInlineRule.Type.ALWAYS));
+      } else if (acceptString("neverclassinline")) {
+        rule =
+            parseRuleWithClassSpec(
+                optionStart, ClassInlineRule.builder().setType(ClassInlineRule.Type.NEVER));
+      } else if (acceptString("neverinline")) {
+        rule =
+            parseRuleWithClassSpec(optionStart, InlineRule.builder().setType(InlineRuleType.NEVER));
+      } else if (acceptString("neversinglecallerinline")) {
+        rule =
+            parseRuleWithClassSpec(
+                optionStart, InlineRule.builder().setType(InlineRuleType.NEVER_SINGLE_CALLER));
+      } else if (acceptString(NoAccessModificationRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoAccessModificationRule.builder());
+      } else if (acceptString(NoFieldTypeStrengtheningRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoFieldTypeStrengtheningRule.builder());
+      } else if (acceptString(NoUnusedInterfaceRemovalRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoUnusedInterfaceRemovalRule.builder());
+      } else if (acceptString(NoVerticalClassMergingRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoVerticalClassMergingRule.builder());
+      } else if (acceptString(NoHorizontalClassMergingRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoHorizontalClassMergingRule.builder());
+      } else if (acceptString(NoMethodStaticizingRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoMethodStaticizingRule.builder());
+      } else if (acceptString(NoParameterReorderingRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoParameterReorderingRule.builder());
+      } else if (acceptString(NoParameterTypeStrengtheningRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoParameterTypeStrengtheningRule.builder());
+      } else if (acceptString(NoRedundantFieldLoadEliminationRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoRedundantFieldLoadEliminationRule.builder());
+      } else if (acceptString(NoReturnTypeStrengtheningRule.RULE_NAME)) {
+        rule = parseRuleWithClassSpec(optionStart, NoReturnTypeStrengtheningRule.builder());
+      } else if (acceptString("neverpropagatevalue")) {
+        rule = parseRuleWithClassSpec(optionStart, NoValuePropagationRule.builder());
+      } else if (acceptString("neverreprocessclassinitializer")) {
+        rule =
+            parseRuleWithClassSpec(
+                optionStart,
+                ReprocessClassInitializerRule.builder()
+                    .setType(ReprocessClassInitializerRule.Type.NEVER));
+      } else if (acceptString("neverreprocessmethod")) {
+        rule =
+            parseRuleWithClassSpec(
+                optionStart, ReprocessMethodRule.builder().setType(ReprocessMethodRule.Type.NEVER));
+      } else if (acceptString("reprocessclassinitializer")) {
+        rule =
+            parseRuleWithClassSpec(
+                optionStart,
+                ReprocessClassInitializerRule.builder()
+                    .setType(ReprocessClassInitializerRule.Type.ALWAYS));
+      } else if (acceptString("reprocessmethod")) {
+        rule =
+            parseRuleWithClassSpec(
+                optionStart,
+                ReprocessMethodRule.builder().setType(ReprocessMethodRule.Type.ALWAYS));
+      } else {
+        return false;
+      }
+      configurationConsumer.addRule(rule, this, optionStart);
+      return true;
     }
 
     private RuntimeException unknownOption(String unknownOption, TextPosition optionStart) {
@@ -709,43 +627,66 @@
           }
         }
       }
+      configurationConsumer.addIgnoredOption(option, this, optionStart);
       warnIgnoringOptions(option, optionStart);
       return true;
     }
 
     private boolean parseIgnoredOption(TextPosition optionStart)
         throws ProguardRuleParserException {
-      return Iterables.any(IGNORED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg)
-          || Iterables.any(
-              IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS, this::skipOptionWithOptionalSingleArg)
-          || Iterables.any(IGNORED_FLAG_OPTIONS, this::skipFlag)
-          || Iterables.any(IGNORED_CLASS_DESCRIPTOR_OPTIONS, this::skipOptionWithClassSpec)
-          || parseOptimizationOption(optionStart);
+      String option =
+          Iterables.find(IGNORED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg, null);
+      if (option == null) {
+        option =
+            Iterables.find(
+                IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS, this::skipOptionWithOptionalSingleArg, null);
+        if (option == null) {
+          option = Iterables.find(IGNORED_FLAG_OPTIONS, this::skipFlag, null);
+          if (option == null) {
+            option =
+                Iterables.find(
+                    IGNORED_CLASS_DESCRIPTOR_OPTIONS, this::skipOptionWithClassSpec, null);
+            if (option == null) {
+              if (parseOptimizationOption(optionStart)) {
+                option = "optimizations";
+              } else {
+                return false;
+              }
+            }
+          }
+        }
+      }
+      configurationConsumer.addIgnoredOption(option, this, optionStart);
+      return true;
     }
 
-    private void parseInclude() throws ProguardRuleParserException {
-      TextPosition start = getPosition();
-      Path included = parseFileInputDependency(inputDependencyConsumer::acceptProguardInclude);
+    private void enqueueInclude(TextPosition optionStart) throws ProguardRuleParserException {
+      Path includePath = parseFileInputDependency(inputDependencyConsumer::acceptProguardInclude);
+      configurationConsumer.addInclude(includePath, this, optionStart);
+    }
+
+    private void parseInclude(Path includePath, TextPosition includePositionStart)
+        throws ProguardRuleParserException {
       try {
-        new ProguardConfigurationSourceParser(new ProguardConfigurationSourceFile(included))
+        new ProguardConfigurationSourceParser(new ProguardConfigurationSourceFile(includePath))
             .parse();
       } catch (FileNotFoundException | NoSuchFileException e) {
-        throw parseError("Included file '" + included.toString() + "' not found",
-            start, e);
+        throw parseError("Included file '" + includePath + "' not found", includePositionStart, e);
       } catch (IOException e) {
-        throw parseError("Failed to read included file '" + included.toString() + "'",
-            start, e);
+        throw parseError(
+            "Failed to read included file '" + includePath + "'", includePositionStart, e);
       }
     }
 
-    private boolean acceptArobaseInclude() throws ProguardRuleParserException {
+    private boolean acceptArobaseInclude(TextPosition optionStart)
+        throws ProguardRuleParserException {
       if (remainingChars() < 2) {
         return false;
       }
       if (!acceptChar('@')) {
         return false;
       }
-      parseInclude();
+      enqueueInclude(optionStart);
       return true;
     }
 
@@ -768,7 +709,7 @@
         }
       }
       configurationConsumer.addKeepAttributePatterns(
-          attributesPatterns, origin, this, getPosition(start), start);
+          attributesPatterns, this, getPosition(start), start);
     }
 
     private boolean skipFlag(String name) {
@@ -914,45 +855,48 @@
           "Expecting '-keep' option after '-if' option.", origin, getPosition(optionStart)));
     }
 
-    private boolean parseMaximumRemovedAndroidLogLevelRule(Position start)
+    private boolean parseMaximumRemovedAndroidLogLevelRule(TextPosition optionStart)
         throws ProguardRuleParserException {
-      if (acceptString("maximumremovedandroidloglevel")) {
-        skipWhitespace();
-        // First parse the mandatory log level int.
-        Integer maxRemovedAndroidLogLevel = acceptInteger();
-        if (maxRemovedAndroidLogLevel == null
-            || maxRemovedAndroidLogLevel < MaximumRemovedAndroidLogLevelRule.NONE) {
-          throw parseError("Expected integer greater than or equal to 1", getPosition());
-        }
-        MaximumRemovedAndroidLogLevelRule.Builder builder =
-            MaximumRemovedAndroidLogLevelRule.builder()
-                .setMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel)
-                .setOrigin(origin)
-                .setStart(start);
-        // Check if we can parse any class annotations or flag.
-        if (parseClassAnnotationsAndFlags(builder)) {
-          // Parse the remainder of the class specification.
-          parseClassSpecFromClassTypeInclusive(builder, false);
-        } else {
-          // Otherwise check if we can parse a class name.
-          parseClassType(
-              builder,
-              // Parse the remainder of the class specification.
-              () -> parseClassSpecFromClassNameInclusive(builder, false),
-              // In case of an error, move position back to the place we expected an (optional)
-              // class type.
-              expectedClassTypeStart -> position = expectedClassTypeStart.getOffsetAsInt());
-        }
-        if (builder.hasClassType()) {
-          Position end = getPosition();
-          configurationConsumer.addRule(
-              builder.setEnd(end).setSource(getSourceSnippet(contents, start, end)).build());
-        } else {
-          configurationConsumer.joinMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
-        }
-        return true;
+      if (!acceptString("maximumremovedandroidloglevel")) {
+        return false;
       }
-      return false;
+      skipWhitespace();
+      // First parse the mandatory log level int.
+      Integer maxRemovedAndroidLogLevel = acceptInteger();
+      if (maxRemovedAndroidLogLevel == null
+          || maxRemovedAndroidLogLevel < MaximumRemovedAndroidLogLevelRule.NONE) {
+        throw parseError("Expected integer greater than or equal to 1", getPosition());
+      }
+      MaximumRemovedAndroidLogLevelRule.Builder builder =
+          MaximumRemovedAndroidLogLevelRule.builder()
+              .setMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel)
+              .setOrigin(origin)
+              .setStart(optionStart);
+      // Check if we can parse any class annotations or flag.
+      if (parseClassAnnotationsAndFlags(builder)) {
+        // Parse the remainder of the class specification.
+        parseClassSpecFromClassTypeInclusive(builder, false);
+      } else {
+        // Otherwise check if we can parse a class name.
+        parseClassType(
+            builder,
+            // Parse the remainder of the class specification.
+            () -> parseClassSpecFromClassNameInclusive(builder, false),
+            // In case of an error, move position back to the place we expected an (optional)
+            // class type.
+            expectedClassTypeStart -> position = expectedClassTypeStart.getOffsetAsInt());
+      }
+      if (builder.hasClassType()) {
+        Position end = getPosition();
+        configurationConsumer.addRule(
+            builder.setEnd(end).setSource(getSourceSnippet(contents, optionStart, end)).build(),
+            this,
+            optionStart);
+      } else {
+        configurationConsumer.joinMaxRemovedAndroidLogLevel(
+            maxRemovedAndroidLogLevel, this, optionStart);
+      }
+      return true;
     }
 
     void verifyAndLinkBackReferences(Iterable<ProguardWildcard> wildcards) {
@@ -1550,20 +1494,28 @@
           }
         }
       }
-
-      if (copied == 0) return fileName;
-
-      result.append(fileName.substring(copied, fileName.length()));
+      if (copied == 0) {
+        return fileName;
+      }
+      result.append(fileName.substring(copied));
       return result.toString();
     }
 
     private Path parseFileInputDependency(BiConsumer<Origin, Path> dependencyConsumer)
         throws ProguardRuleParserException {
-      Path file = parseFileName(false);
+      Path file = parseFileName();
       dependencyConsumer.accept(origin, file);
       return file;
     }
 
+    private Path parseOptionalFileName() throws ProguardRuleParserException {
+      return isOptionalArgumentGiven() ? parseFileName() : null;
+    }
+
+    private Path parseFileName() throws ProguardRuleParserException {
+      return parseFileName(false);
+    }
+
     private Path parseFileName(boolean stopAfterPathSeparator) throws ProguardRuleParserException {
       TextPosition start = getPosition();
       skipWhitespace();
@@ -2026,6 +1978,9 @@
       if (isQuote(quote)) {
         expectClosingQuote(quote);
       }
+      int lastPatternEndLine = line;
+      int lastPatternEndLineStartPosition = lineStartPosition;
+      int lastPatternEndPosition = position;
       while (pattern != null) {
         patterns.add(pattern);
         skipWhitespace();
@@ -2037,17 +1992,24 @@
           if (isQuote(quote)) {
             expectClosingQuote(quote);
           }
+          lastPatternEndLine = line;
+          lastPatternEndLineStartPosition = lineStartPosition;
+          lastPatternEndPosition = position;
           if (pattern == null) {
             throw parseError("Expected list element", start);
           }
         } else {
-          pattern = null;
+          break;
         }
       }
       skipWhitespace();
       if (!eof() && !hasNextChar('-') && !hasNextChar('@')) {
         throw parseError("Unexpected attribute");
       }
+      // Position the parser at the end of the rule before notifying the configuration consumer.
+      line = lastPatternEndLine;
+      lineStartPosition = lastPatternEndLineStartPosition;
+      position = lastPatternEndPosition;
       return patterns;
     }
 
@@ -2086,15 +2048,11 @@
       }
     }
 
-    private void parseClassFilter(Consumer<ProguardClassNameList> consumer)
-        throws ProguardRuleParserException {
+    private ProguardClassNameList parseOptionalClassFilter() throws ProguardRuleParserException {
       skipWhitespace();
-      if (isOptionalArgumentGiven()) {
-        consumer.accept(parseClassNames());
-      } else {
-        consumer.accept(
-            ProguardClassNameList.singletonList(ProguardTypeMatcher.defaultAllMatcher()));
-      }
+      return isOptionalArgumentGiven()
+          ? parseClassNames()
+          : ProguardClassNameList.singletonList(ProguardTypeMatcher.defaultAllMatcher());
     }
 
     private void parseClassNameAddToBuilder(ProguardClassNameList.Builder builder)
@@ -2139,14 +2097,9 @@
       return character != ',' && !Character.isWhitespace(character);
     }
 
-    private void parsePathFilter(Consumer<ProguardPathList> consumer)
-        throws ProguardRuleParserException {
+    private ProguardPathList parseOptionalPathFilter() throws ProguardRuleParserException {
       skipWhitespace();
-      if (isOptionalArgumentGiven()) {
-        consumer.accept(parsePathFilter());
-      } else {
-        consumer.accept(ProguardPathList.emptyList());
-      }
+      return isOptionalArgumentGiven() ? parsePathFilter() : ProguardPathList.emptyList();
     }
 
     private ProguardPathList parsePathFilter() throws ProguardRuleParserException {
@@ -2234,6 +2187,10 @@
           "Ignoring modifier: " + modifier, origin, getPosition(start)));
     }
 
+    int getOffset() {
+      return position;
+    }
+
     private Position getPosition(TextPosition start) {
       if (start.getOffset() == position) {
         return start;
@@ -2327,4 +2284,17 @@
       this.negated = negated;
     }
   }
+
+  static class IncludeWorkItem {
+
+    final Path includePath;
+    final TextPosition includePositionStart;
+    final int includePositionEnd;
+
+    IncludeWorkItem(Path includePath, TextPosition includePositionStart, int includePositionEnd) {
+      this.includePath = includePath;
+      this.includePositionStart = includePositionStart;
+      this.includePositionEnd = includePositionEnd;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
index ea658c5..b3067da 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser;
@@ -13,88 +12,168 @@
 
 public interface ProguardConfigurationParserConsumer {
 
-  void addParsedConfiguration(String s);
+  void addBaseDirectory(
+      Path baseDirectory, ProguardConfigurationSourceParser parser, TextPosition positionStart);
 
-  void addRule(ProguardConfigurationRule rule);
+  void addIgnoredOption(
+      String option, ProguardConfigurationSourceParser parser, TextPosition positionStart);
+
+  void addInclude(
+      Path includePath, ProguardConfigurationSourceParser parser, TextPosition positionStart);
+
+  void addLeadingBOM();
+
+  void addParsedConfiguration(ProguardConfigurationSourceParser parser);
+
+  void addRule(
+      ProguardConfigurationRule rule,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart);
 
   void addKeepAttributePatterns(
       List<String> attributesPatterns,
-      Origin origin,
       ProguardConfigurationSourceParser parser,
       Position position,
       TextPosition positionStart);
 
-  void setRenameSourceFileAttribute(String s, Origin origin, Position position);
+  void addKeepKotlinMetadata(
+      ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart);
 
-  void addKeepPackageNamesPattern(ProguardClassNameList proguardClassNameList);
+  void addKeepPackageNamesPattern(
+      ProguardClassNameList proguardClassNameList,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart);
 
-  void setKeepParameterNames(boolean b, Origin origin, Position position);
+  void setKeepParameterNames(
+      ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart);
 
-  void enableKeepDirectories();
+  void setRenameSourceFileAttribute(
+      String s,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void addKeepDirectories(ProguardPathList proguardPathList);
+  void enableKeepDirectories(
+      ProguardPathList keepDirectoryPatterns,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart);
 
-  void disableOptimization(Origin origin, Position position);
+  void enablePrintConfiguration(
+      Path printConfigurationFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void disableObfuscation(Origin origin, Position position);
+  void enablePrintMapping(
+      Path printMappingFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void disableShrinking(Origin origin, Position position);
+  void enablePrintSeeds(
+      Path printSeedsFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void setPrintUsage(boolean b);
+  void enablePrintUsage(
+      Path printUsageFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void setPrintUsageFile(Path path);
+  void disableOptimization(ProguardConfigurationSourceParser parser, Position position);
 
-  void enableProtoShrinking();
+  void disableObfuscation(ProguardConfigurationSourceParser parser, Position position);
 
-  void setIgnoreWarnings(boolean b);
+  void disableShrinking(ProguardConfigurationSourceParser parser, Position position);
 
-  void addDontWarnPattern(ProguardClassNameList pattern);
+  void enableProtoShrinking(ProguardConfigurationSourceParser parser, TextPosition positionStart);
 
-  void addDontNotePattern(ProguardClassNameList pattern);
+  void setIgnoreWarnings(ProguardConfigurationSourceParser parser, TextPosition positionStart);
 
-  void enableAllowAccessModification(Origin origin, Position position);
+  void addDontWarnPattern(
+      ProguardClassNameList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart);
 
-  void enablePrintConfiguration(Origin origin, Position position);
+  void addDontNotePattern(
+      ProguardClassNameList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart);
 
-  void setPrintConfigurationFile(Path path);
+  void enableAllowAccessModification(
+      ProguardConfigurationSourceParser parser, Position position, TextPosition positionStart);
 
-  void enablePrintMapping(Origin origin, Position position);
+  void setApplyMappingFile(
+      Path applyMappingFile,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void setPrintMappingFile(Path path);
+  void addInjars(
+      List<FilteredClassPath> filteredClassPaths,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void setApplyMappingFile(Path path, Origin origin, Position position);
+  void addLibraryJars(
+      List<FilteredClassPath> filteredClassPaths,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void addInjars(List<FilteredClassPath> filteredClassPaths, Origin origin, Position position);
+  void setObfuscationDictionary(
+      Path path,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void addLibraryJars(List<FilteredClassPath> filteredClassPaths, Origin origin, Position position);
+  void setClassObfuscationDictionary(
+      Path path,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void setPrintSeeds(boolean b, Origin origin, Position position);
+  void setPackageObfuscationDictionary(
+      Path path,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void setSeedFile(Path path);
+  void addAdaptClassStringsPattern(
+      ProguardClassNameList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart);
 
-  void setObfuscationDictionary(Path path, Origin origin, Position position);
+  void addAdaptResourceFileContents(
+      ProguardPathList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart);
 
-  void setClassObfuscationDictionary(Path path, Origin origin, Position position);
+  void addAdaptResourceFilenames(
+      ProguardPathList pattern,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart);
 
-  void setPackageObfuscationDictionary(Path path, Origin origin, Position position);
-
-  void addAdaptClassStringsPattern(ProguardClassNameList pattern);
-
-  void addAdaptResourceFileContents(ProguardPathList pattern);
-
-  void addAdaptResourceFilenames(ProguardPathList pattern);
-
-  void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel);
+  void joinMaxRemovedAndroidLogLevel(
+      int maxRemovedAndroidLogLevel,
+      ProguardConfigurationSourceParser parser,
+      TextPosition positionStart);
 
   PackageObfuscationMode getPackageObfuscationMode();
 
-  void setPackagePrefix(String s);
+  void enableRepackageClasses(
+      String packagePrefix,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
-  void setFlattenPackagePrefix(String s);
-
-  void enableRepackageClasses(Origin origin, Position position);
-
-  void enableFlattenPackageHierarchy(Origin origin, Position position);
+  void enableFlattenPackageHierarchy(
+      String packagePrefix,
+      ProguardConfigurationSourceParser parser,
+      Position position,
+      TextPosition positionStart);
 
   default void addWhitespace(ProguardConfigurationSourceParser parser, TextPosition position) {}
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
index b7392e7..6ebba5a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
@@ -10,7 +10,6 @@
 
   private final boolean enableLegacyFullModeForKeepRules;
   private final boolean enableLegacyFullModeForKeepRulesWarnings;
-  private final boolean enableExperimentalCheckEnumUnboxed;
   private final boolean enableKeepRuntimeInvisibleAnnotations;
   private final boolean enableTestingOptions;
   private final boolean forceProguardCompatibility;
@@ -18,11 +17,9 @@
   ProguardConfigurationParserOptions(
       boolean enableLegacyFullModeForKeepRules,
       boolean enableLegacyFullModeForKeepRulesWarnings,
-      boolean enableExperimentalCheckEnumUnboxed,
       boolean enableKeepRuntimeInvisibleAnnotations,
       boolean enableTestingOptions,
       boolean forceProguardCompatibility) {
-    this.enableExperimentalCheckEnumUnboxed = enableExperimentalCheckEnumUnboxed;
     this.enableKeepRuntimeInvisibleAnnotations = enableKeepRuntimeInvisibleAnnotations;
     this.enableTestingOptions = enableTestingOptions;
     this.enableLegacyFullModeForKeepRules = enableLegacyFullModeForKeepRules;
@@ -44,10 +41,6 @@
     return !forceProguardCompatibility && enableLegacyFullModeForKeepRulesWarnings;
   }
 
-  public boolean isExperimentalCheckEnumUnboxedEnabled() {
-    return enableExperimentalCheckEnumUnboxed;
-  }
-
   public boolean isKeepRuntimeInvisibleAnnotationsEnabled() {
     return enableKeepRuntimeInvisibleAnnotations;
   }
@@ -60,7 +53,6 @@
 
     private boolean enableLegacyFullModeForKeepRules = true;
     private boolean enableLegacyFullModeForKeepRulesWarnings = false;
-    private boolean enableExperimentalCheckEnumUnboxed;
     private boolean enableKeepRuntimeInvisibleAnnotations = true;
     private boolean enableTestingOptions;
     private boolean forceProguardCompatibility = false;
@@ -72,9 +64,6 @@
       enableLegacyFullModeForKeepRulesWarnings =
           parseSystemPropertyOrDefault(
               "com.android.tools.r8.enableLegacyFullModeForKeepRulesWarnings", false);
-      enableExperimentalCheckEnumUnboxed =
-          parseSystemPropertyOrDefault(
-              "com.android.tools.r8.experimental.enablecheckenumunboxed", false);
       enableKeepRuntimeInvisibleAnnotations =
           parseSystemPropertyOrDefault(
               "com.android.tools.r8.enableKeepRuntimeInvisibleAnnotations", true);
@@ -94,12 +83,6 @@
       return this;
     }
 
-    public Builder setEnableExperimentalCheckEnumUnboxed(
-        boolean enableExperimentalCheckEnumUnboxed) {
-      this.enableExperimentalCheckEnumUnboxed = enableExperimentalCheckEnumUnboxed;
-      return this;
-    }
-
     public Builder setEnableTestingOptions(boolean enableTestingOptions) {
       this.enableTestingOptions = enableTestingOptions;
       return this;
@@ -114,7 +97,6 @@
       return new ProguardConfigurationParserOptions(
           enableLegacyFullModeForKeepRules,
           enableLegacyFullModeForKeepRulesWarnings,
-          enableExperimentalCheckEnumUnboxed,
           enableKeepRuntimeInvisibleAnnotations,
           enableTestingOptions,
           forceProguardCompatibility);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index cf407e6..e0848b0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -224,60 +224,58 @@
 
   public StringBuilder append(StringBuilder builder) {
     List<String> attributes = new ArrayList<>();
-    if (sourceFile) {
-      attributes.add(SOURCE_FILE);
-    }
-    if (innerClasses) {
-      attributes.add(INNER_CLASSES);
+    if (annotationDefault) {
+      attributes.add(ANNOTATION_DEFAULT);
     }
     if (enclosingMethod) {
       attributes.add(ENCLOSING_METHOD);
     }
-    if (signature) {
-      attributes.add(SIGNATURE);
-    }
     if (exceptions) {
       attributes.add(EXCEPTIONS);
     }
+    if (innerClasses) {
+      attributes.add(INNER_CLASSES);
+    }
     if (methodParameters) {
       attributes.add(METHOD_PARAMETERS);
     }
-    if (sourceDebugExtension) {
-      attributes.add(SOURCE_DEBUG_EXTENSION);
-    }
-    if (runtimeVisibleAnnotations) {
-      attributes.add(RUNTIME_VISIBLE_ANNOTATIONS);
+    if (permittedSubclasses) {
+      attributes.add(PERMITTED_SUBCLASSES);
     }
     if (runtimeInvisibleAnnotations) {
       attributes.add(RUNTIME_INVISIBLE_ANNOTATIONS);
     }
-    if (runtimeVisibleParameterAnnotations) {
-      attributes.add(RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS);
-    }
     if (runtimeInvisibleParameterAnnotations) {
       attributes.add(RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
     }
-    if (runtimeVisibleTypeAnnotations) {
-      attributes.add(RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
-    }
     if (runtimeInvisibleTypeAnnotations) {
       attributes.add(RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
     }
-    if (annotationDefault) {
-      attributes.add(ANNOTATION_DEFAULT);
+    if (runtimeVisibleAnnotations) {
+      attributes.add(RUNTIME_VISIBLE_ANNOTATIONS);
+    }
+    if (runtimeVisibleParameterAnnotations) {
+      attributes.add(RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS);
+    }
+    if (runtimeVisibleTypeAnnotations) {
+      attributes.add(RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+    }
+    if (signature) {
+      attributes.add(SIGNATURE);
+    }
+    if (sourceDebugExtension) {
+      attributes.add(SOURCE_DEBUG_EXTENSION);
+    }
+    if (sourceFile) {
+      attributes.add(SOURCE_FILE);
     }
     if (stackMapTable) {
       attributes.add(STACK_MAP_TABLE);
     }
-    if (permittedSubclasses) {
-      attributes.add(PERMITTED_SUBCLASSES);
-    }
-
-    if (attributes.size() > 0) {
+    if (!attributes.isEmpty()) {
       builder.append("-keepattributes ");
       builder.append(String.join(",", attributes));
     }
-
     return builder;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 2541fcb..3906ea2 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -138,7 +138,6 @@
         DependentMinimumKeepInfoCollection.createConcurrent();
     private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
     private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
-    private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
     private final Set<DexMethod> reprocess = Sets.newIdentityHashSet();
     private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
     private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
@@ -444,8 +443,7 @@
         evaluateCheckDiscardRule(clazz, rule.asProguardCheckDiscardRule());
       } else if (rule instanceof CheckEnumUnboxedRule) {
         evaluateCheckEnumUnboxedRule(clazz, (CheckEnumUnboxedRule) rule);
-      } else if (rule instanceof NoAccessModificationRule
-          || rule instanceof ProguardWhyAreYouKeepingRule) {
+      } else if (rule instanceof NoAccessModificationRule || rule instanceof WhyAreYouKeepingRule) {
         markClass(clazz, rule, ifRulePreconditionMatch);
         markMatchingVisibleMethods(
             clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch);
@@ -499,9 +497,14 @@
       } else if (rule instanceof ProguardIdentifierNameStringRule) {
         markMatchingFields(clazz, fieldKeepRules, rule, ifRulePreconditionMatch);
         markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
-      } else {
-        assert rule instanceof ConvertCheckNotNullRule;
+      } else if (rule instanceof ConvertCheckNotNullRule) {
         markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
+      } else if (rule instanceof WhyAreYouNotObfuscatingRule) {
+        markClass(clazz, rule, ifRulePreconditionMatch);
+        markMatchingFields(clazz, fieldKeepRules, rule, ifRulePreconditionMatch);
+        markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
+      } else {
+        assert false : rule.getClass().getName();
       }
     }
 
@@ -658,7 +661,6 @@
           dependentMinimumKeepInfo,
           ImmutableList.copyOf(reasonAsked.values()),
           alwaysInline,
-          whyAreYouNotInlining,
           reprocess,
           alwaysClassInline,
           mayHaveSideEffects,
@@ -1441,7 +1443,7 @@
         evaluateAssumeNoSideEffectsRule(item, (ProguardAssumeNoSideEffectRule) context, rule);
       } else if (context instanceof ProguardAssumeValuesRule) {
         evaluateAssumeValuesRule(item, (ProguardAssumeValuesRule) context, rule);
-      } else if (context instanceof ProguardWhyAreYouKeepingRule) {
+      } else if (context instanceof WhyAreYouKeepingRule) {
         reasonAsked.computeIfAbsent(item.getReference(), i -> i);
         context.markAsUsed();
       } else if (context.isProguardCheckDiscardRule()) {
@@ -1482,7 +1484,15 @@
         if (!item.isMethod()) {
           throw new Unreachable();
         }
-        whyAreYouNotInlining.add(item.asMethod().getReference());
+        dependentMinimumKeepInfo
+            .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+            .asMethodJoiner()
+            .setWhyAreYouNotInlining();
+        context.markAsUsed();
+      } else if (context instanceof WhyAreYouNotObfuscatingRule) {
+        dependentMinimumKeepInfo
+            .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+            .setWhyAreYouNotObfuscating();
         context.markAsUsed();
       } else if (context.isClassInlineRule()) {
         ClassInlineRule classInlineRule = context.asClassInlineRule();
@@ -2202,7 +2212,6 @@
 
     public final ImmutableList<DexReference> reasonAsked;
     public final Set<DexMethod> alwaysInline;
-    public final Set<DexMethod> whyAreYouNotInlining;
     public final Set<DexMethod> reprocess;
     public final PredicateSet<DexType> alwaysClassInline;
     public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
@@ -2215,7 +2224,6 @@
         DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         ImmutableList<DexReference> reasonAsked,
         Set<DexMethod> alwaysInline,
-        Set<DexMethod> whyAreYouNotInlining,
         Set<DexMethod> reprocess,
         PredicateSet<DexType> alwaysClassInline,
         Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
@@ -2233,7 +2241,6 @@
           pendingMethodMoveInverse);
       this.reasonAsked = reasonAsked;
       this.alwaysInline = alwaysInline;
-      this.whyAreYouNotInlining = whyAreYouNotInlining;
       this.reprocess = reprocess;
       this.alwaysClassInline = alwaysClassInline;
       this.mayHaveSideEffects = mayHaveSideEffects;
@@ -2347,7 +2354,6 @@
                 getDependentMinimumKeepInfo().rewrittenWithLens(graphLens, timing),
                 reasonAsked,
                 alwaysInline,
-                whyAreYouNotInlining,
                 reprocess,
                 alwaysClassInline,
                 mayHaveSideEffects,
@@ -2648,7 +2654,6 @@
           reasonAsked,
           Collections.emptySet(),
           Collections.emptySet(),
-          Collections.emptySet(),
           PredicateSet.empty(),
           emptyMap(),
           emptyMap(),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingRule.java
similarity index 86%
rename from src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
rename to src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingRule.java
index cffaab1..e04c6e6 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingRule.java
@@ -7,11 +7,11 @@
 import com.android.tools.r8.position.Position;
 import java.util.List;
 
-public class ProguardWhyAreYouKeepingRule extends ProguardConfigurationRule {
+public class WhyAreYouKeepingRule extends ProguardConfigurationRule {
 
   @SuppressWarnings("NonCanonicalType")
   public static class Builder
-      extends ProguardConfigurationRule.Builder<ProguardWhyAreYouKeepingRule, Builder> {
+      extends ProguardConfigurationRule.Builder<WhyAreYouKeepingRule, Builder> {
 
     private Builder() {
       super();
@@ -23,8 +23,8 @@
     }
 
     @Override
-    public ProguardWhyAreYouKeepingRule build() {
-      return new ProguardWhyAreYouKeepingRule(
+    public WhyAreYouKeepingRule build() {
+      return new WhyAreYouKeepingRule(
           origin,
           getPosition(),
           source,
@@ -41,7 +41,7 @@
     }
   }
 
-  private ProguardWhyAreYouKeepingRule(
+  private WhyAreYouKeepingRule(
       Origin origin,
       Position position,
       String source,
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
index 4801930..f114450 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
@@ -77,6 +77,13 @@
     return new Builder();
   }
 
+  // The ServiceLoader rewriter emits debug information if -whyareyounotinlining matches
+  // ServiceLoader.load.
+  @Override
+  public boolean isApplicableToLibraryClasses() {
+    return true;
+  }
+
   @Override
   String typeString() {
     return RULE_NAME;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotObfuscatingRule.java
similarity index 79%
copy from src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
copy to src/main/java/com/android/tools/r8/shaking/WhyAreYouNotObfuscatingRule.java
index cffaab1..b6b291b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotObfuscatingRule.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2025, 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.shaking;
@@ -7,11 +7,13 @@
 import com.android.tools.r8.position.Position;
 import java.util.List;
 
-public class ProguardWhyAreYouKeepingRule extends ProguardConfigurationRule {
+public class WhyAreYouNotObfuscatingRule extends ProguardConfigurationRule {
+
+  public static final String RULE_NAME = "whyareyounotobfuscating";
 
   @SuppressWarnings("NonCanonicalType")
   public static class Builder
-      extends ProguardConfigurationRule.Builder<ProguardWhyAreYouKeepingRule, Builder> {
+      extends ProguardConfigurationRule.Builder<WhyAreYouNotObfuscatingRule, Builder> {
 
     private Builder() {
       super();
@@ -23,8 +25,8 @@
     }
 
     @Override
-    public ProguardWhyAreYouKeepingRule build() {
-      return new ProguardWhyAreYouKeepingRule(
+    public WhyAreYouNotObfuscatingRule build() {
+      return new WhyAreYouNotObfuscatingRule(
           origin,
           getPosition(),
           source,
@@ -41,7 +43,7 @@
     }
   }
 
-  private ProguardWhyAreYouKeepingRule(
+  private WhyAreYouNotObfuscatingRule(
       Origin origin,
       Position position,
       String source,
@@ -77,6 +79,6 @@
 
   @Override
   String typeString() {
-    return "whyareyoukeeping";
+    return RULE_NAME;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index 27ae067..89992ae 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -186,7 +186,6 @@
     return method.isStatic()
         && method.isNonAbstractNonNativeMethod()
         && method.isPublic()
-        && method.annotations().isEmpty()
         && method.getParameterAnnotations().isEmpty();
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index c0f2cbc..8ab29e7 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -407,12 +407,13 @@
     return Character.isWhitespace(codePoint) || isBOM(codePoint);
   }
 
+  public static boolean hasLeadingBOM(String s) {
+    return !s.isEmpty() && s.charAt(0) == StringUtils.BOM;
+  }
+
   public static String stripLeadingBOM(String s) {
-    if (s.length() > 0 && s.charAt(0) == StringUtils.BOM) {
-      return s.substring(1);
-    } else {
-      return s;
-    }
+    assert hasLeadingBOM(s);
+    return s.substring(1);
   }
 
   public static String trim(String s) {
diff --git a/src/test/java/com/android/tools/r8/assistant/JavaLangClassJsonTest.java b/src/test/java/com/android/tools/r8/assistant/JavaLangClassJsonTest.java
index 5e1fbf8..ff05d32 100644
--- a/src/test/java/com/android/tools/r8/assistant/JavaLangClassJsonTest.java
+++ b/src/test/java/com/android/tools/r8/assistant/JavaLangClassJsonTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.assistant.postprocessing.model.ClassGetMember;
 import com.android.tools.r8.assistant.postprocessing.model.ClassGetMembers;
 import com.android.tools.r8.assistant.postprocessing.model.ClassGetName;
+import com.android.tools.r8.assistant.postprocessing.model.ClassNewInstance;
 import com.android.tools.r8.assistant.postprocessing.model.ReflectiveEvent;
 import com.android.tools.r8.assistant.runtime.ReflectiveEventType;
 import com.android.tools.r8.assistant.runtime.ReflectiveOperationJsonLogger;
@@ -69,7 +70,7 @@
         .assertSuccess();
     List<ReflectiveEvent> reflectiveEvents =
         new ReflectiveOperationJsonParser(factoryBox.get()).parse(path);
-    Assert.assertEquals(29, reflectiveEvents.size());
+    Assert.assertEquals(30, reflectiveEvents.size());
 
     assertTrue(reflectiveEvents.get(4).isClassGetMember());
     ClassGetMember updater00 = reflectiveEvents.get(4).asClassGetMember();
@@ -163,6 +164,11 @@
         Reference.methodFromMethod(Bar.class.getConstructor()),
         updater24.getMember().asDexMethod().asMethodReference());
 
+    assertTrue(reflectiveEvents.get(29).isClassNewInstance());
+    ClassNewInstance updater29 = reflectiveEvents.get(29).asClassNewInstance();
+    assertEquals(ReflectiveEventType.CLASS_NEW_INSTANCE, updater29.getEventType());
+    assertEquals(Bar.class.getName(), updater29.getType().toSourceString());
+
     Box<KeepInfoCollectionExported> keepInfoBox = new Box<>();
     testForR8(parameters)
         .addProgramClasses(JavaLangClassTestClass.class, Foo.class, Bar.class)
@@ -188,6 +194,7 @@
             "public com.android.tools.r8.assistant.JavaLangClassTestClass$Bar()",
             "true",
             "class com.android.tools.r8.assistant.JavaLangClassTestClass$Bar",
+            "11",
             "END");
     KeepInfoCollectionExported keepInfoCollectionExported = keepInfoBox.get();
 
diff --git a/src/test/java/com/android/tools/r8/assistant/JavaLangClassTest.java b/src/test/java/com/android/tools/r8/assistant/JavaLangClassTest.java
index 374014b..ce78e14 100644
--- a/src/test/java/com/android/tools/r8/assistant/JavaLangClassTest.java
+++ b/src/test/java/com/android/tools/r8/assistant/JavaLangClassTest.java
@@ -85,6 +85,8 @@
             "true",
             "40",
             "class com.android.tools.r8.assistant.JavaLangClassTestClass$Bar",
+            "50",
+            "11",
             "END");
   }
 
@@ -201,7 +203,7 @@
 
     @Override
     public void onClassNewInstance(Stack stack, Class<?> clazz) {
-      super.onClassNewInstance(stack, clazz);
+      printNumIfTrue(clazz.getName().endsWith("Bar"), 50);
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/assistant/JavaLangClassTestClass.java b/src/test/java/com/android/tools/r8/assistant/JavaLangClassTestClass.java
index a0308d6..feb74d1 100644
--- a/src/test/java/com/android/tools/r8/assistant/JavaLangClassTestClass.java
+++ b/src/test/java/com/android/tools/r8/assistant/JavaLangClassTestClass.java
@@ -52,6 +52,13 @@
       Bar cast = Bar.class.cast(o);
       System.out.println(Bar.class.isInstance(o));
       System.out.println(Bar.class.asSubclass(Foo.class));
+      Bar newBar = null;
+      try {
+        newBar = Bar.class.newInstance();
+        System.out.println(newBar.bar());
+      } catch (InstantiationException | IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
       System.out.println("END");
     } catch (ClassNotFoundException | NoSuchFieldException | NoSuchMethodException e) {
       System.out.println("EXCEPTION " + e.getMessage());
diff --git a/src/test/java/com/android/tools/r8/assistant/ProxyJsonTest.java b/src/test/java/com/android/tools/r8/assistant/ProxyJsonTest.java
new file mode 100644
index 0000000..0f75c00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/assistant/ProxyJsonTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2025, 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.assistant;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.assistant.postprocessing.ReflectiveOperationJsonParser;
+import com.android.tools.r8.assistant.postprocessing.model.ProxyNewProxyInstance;
+import com.android.tools.r8.assistant.postprocessing.model.ReflectiveEvent;
+import com.android.tools.r8.assistant.runtime.ReflectiveEventType;
+import com.android.tools.r8.assistant.runtime.ReflectiveOperationJsonLogger;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.shaking.KeepInfoCollectionExported;
+import com.android.tools.r8.utils.Box;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ProxyJsonTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNativeMultidexDexRuntimes().withMaximumApiLevel().build();
+  }
+
+  @Test
+  public void testInstrumentationWithCustomOracle() throws Exception {
+    Path path = Paths.get(temp.newFile().getAbsolutePath());
+    Box<DexItemFactory> factoryBox = new Box<>();
+    testForAssistant()
+        .addProgramClassesAndInnerClasses(ProxyTestClass.class)
+        .addInstrumentationClasses(Instrumentation.class)
+        .setCustomReflectiveOperationReceiver(Instrumentation.class)
+        .setMinApi(parameters)
+        .addOptionsModification(opt -> factoryBox.set(opt.itemFactory))
+        .compile()
+        .addVmArguments("-Dcom.android.tools.r8.reflectiveJsonLogger=" + path)
+        .run(parameters.getRuntime(), ProxyTestClass.class)
+        .assertSuccess();
+    List<ReflectiveEvent> reflectiveEvents =
+        new ReflectiveOperationJsonParser(factoryBox.get()).parse(path);
+    assertEquals(1, reflectiveEvents.size());
+
+    assertTrue(reflectiveEvents.get(0).isProxyNewProxyInstance());
+    ProxyNewProxyInstance updater0 = reflectiveEvents.get(0).asProxyNewProxyInstance();
+    assertEquals(ReflectiveEventType.PROXY_NEW_PROXY_INSTANCE, updater0.getEventType());
+    assertEquals(ProxyTestClass.F.class.getName(), updater0.getInterfaces()[0].toSourceString());
+    assertEquals(1, updater0.getInterfaces().length);
+
+    Box<KeepInfoCollectionExported> keepInfoBox = new Box<>();
+    testForR8(parameters)
+        .addProgramClassesAndInnerClasses(ProxyTestClass.class)
+        .addOptionsModification(
+            opt -> opt.testing.finalKeepInfoCollectionConsumer = keepInfoBox::set)
+        .setMinApi(parameters)
+        .addKeepMainRule(ProxyTestClass.class)
+        .run(parameters.getRuntime(), ProxyTestClass.class)
+        .assertSuccess();
+
+    KeepInfoCollectionExported keepInfoCollectionExported = keepInfoBox.get();
+
+    assertTrue(updater0.isKeptBy(keepInfoCollectionExported));
+  }
+
+  public static class Instrumentation extends ReflectiveOperationJsonLogger {
+    public Instrumentation() throws IOException {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/DefaultInterfaceMethodCollisionInSubclassAfterClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/DefaultInterfaceMethodCollisionInSubclassAfterClassMergingTest.java
index a492bae..996c3b6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/DefaultInterfaceMethodCollisionInSubclassAfterClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/DefaultInterfaceMethodCollisionInSubclassAfterClassMergingTest.java
@@ -53,6 +53,7 @@
                 runResult.assertFailureWithErrorThatThrows(
                     parameters.isCfRuntime()
                             && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)
+                            && parameters.getRuntime().asCf().isOlderThan(CfVm.JDK25)
                         ? AbstractMethodError.class
                         : IncompatibleClassChangeError.class),
             runResult -> runResult.assertSuccessWithOutputLines("A", "I", "J"));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index c0fa6fe..6814cf9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -83,6 +83,7 @@
                 builder.assertFailureWithErrorThatThrows(
                     parameters.isCfRuntime()
                             && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)
+                            && parameters.getRuntime().asCf().isOlderThan(CfVm.JDK25)
                         ? AbstractMethodError.class
                         : IncompatibleClassChangeError.class),
             builder -> builder.assertSuccessWithOutputLines("K", "J"));
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
index be7017e..69417de 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
@@ -98,8 +98,10 @@
   }
 
   private void checkResult(TestRunResult<?> result) {
-    if (parameters.isCfRuntime() && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
-      // TODO(b/145566657): For some reason JDK11+ throws AbstractMethodError.
+    if (parameters.isCfRuntime()
+        && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)
+        && parameters.getRuntime().asCf().isOlderThan(CfVm.JDK25)) {
+      // For some reason [JDK11,JDK25[ throws AbstractMethodError (see b/145566657).
       result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
     } else {
       result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java
index 78dcfeb..3d7dd8f 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java
@@ -32,7 +32,10 @@
   @Parameter(0)
   public TestParameters parameters;
 
-  static final Matcher<String> EXPECTED = containsString("cannot inherit from sealed class");
+  static final Matcher<String> EXPECTED_BEFORE_JDK25 =
+      containsString("cannot inherit from sealed class");
+  static final Matcher<String> EXPECTED_FROM_JDK25 =
+      containsString("Failed listed permitted subclass check");
   static final String EXPECTED_WITHOUT_PERMITTED_SUBCLASSES_ATTRIBUTE_OR_FIXED_ATTRIBUTE =
       StringUtils.lines("Sub1", "Sub2");
 
@@ -59,7 +62,10 @@
     testForJvm(parameters)
         .apply(this::addTestClasses)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertFailureWithErrorThatMatches(EXPECTED);
+        .assertFailureWithErrorThatMatches(
+            parameters.isCfRuntime() && parameters.asCfRuntime().isOlderThan(CfVm.JDK25)
+                ? EXPECTED_BEFORE_JDK25
+                : EXPECTED_FROM_JDK25);
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
index 77d9de2..6343728 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
@@ -40,7 +40,10 @@
   @Parameter(1)
   public boolean keepPermittedSubclassesAttribute;
 
-  static final Matcher<String> EXPECTED = containsString("cannot inherit from sealed class");
+  static final Matcher<String> EXPECTED_BEFORE_JDK25 =
+      containsString("cannot inherit from sealed class");
+  static final Matcher<String> EXPECTED_FROM_JDK25 =
+      containsString("Failed listed permitted subclass check");
   static final String EXPECTED_WITHOUT_PERMITTED_SUBCLASSES_ATTRIBUTE =
       StringUtils.lines("Sub1", "Sub2", "Sub3");
 
@@ -69,7 +72,10 @@
     testForJvm(parameters)
         .apply(this::addTestClasses)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertFailureWithErrorThatMatches(EXPECTED);
+        .assertFailureWithErrorThatMatches(
+            parameters.isCfRuntime() && parameters.asCfRuntime().isOlderThan(CfVm.JDK25)
+                ? EXPECTED_BEFORE_JDK25
+                : EXPECTED_FROM_JDK25);
   }
 
   @Test
@@ -81,8 +87,10 @@
         .applyIf(
             DesugarTestConfiguration::isNotJavac,
             r -> r.assertSuccessWithOutput(EXPECTED_WITHOUT_PERMITTED_SUBCLASSES_ATTRIBUTE),
+            c -> parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK25),
+            r -> r.assertFailureWithErrorThatMatches(EXPECTED_FROM_JDK25),
             c -> parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK17),
-            r -> r.assertFailureWithErrorThatMatches(EXPECTED),
+            r -> r.assertFailureWithErrorThatMatches(EXPECTED_BEFORE_JDK25),
             r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
   }
 
@@ -125,8 +133,13 @@
                     && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17)),
             r -> r.assertSuccessWithOutput(EXPECTED_WITHOUT_PERMITTED_SUBCLASSES_ATTRIBUTE),
             parameters.isCfRuntime()
+                && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK25)
                 && keepPermittedSubclassesAttribute,
-            r -> r.assertFailureWithErrorThatMatches(EXPECTED),
+            r -> r.assertFailureWithErrorThatMatches(EXPECTED_FROM_JDK25),
+            parameters.isCfRuntime()
+                && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17)
+                && keepPermittedSubclassesAttribute,
+            r -> r.assertFailureWithErrorThatMatches(EXPECTED_BEFORE_JDK25),
             r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
   }
 
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
index 23dc374..9bade28 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
@@ -56,7 +56,7 @@
                 transformer(Super.class).removeInnerClasses().transform())
             .run(parameters.getRuntime(), Main.class);
     if (parameters.isCfRuntime()) {
-      if (parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK24)) {
+      if (parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK25)) {
         runResult.assertSuccessWithOutputLines(
             "class java.lang.TypeNotPresentException::Type R not present",
             "R",
@@ -91,7 +91,7 @@
                 transformer(Super.class).removeInnerClasses().transform())
             .run(parameters.getRuntime(), Main.class);
     if (parameters.isCfRuntime()) {
-      if (parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK24)) {
+      if (parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK25)) {
         runResult.assertSuccessWithOutputLines(
             Super.class.getTypeName() + "<T>",
             "class java.lang.TypeNotPresentException::Type S not present",
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
index 9331e2c..f3cc39a 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
@@ -60,12 +61,18 @@
       Version version = parameters.getRuntime().asDex().getVm().getVersion();
       if (version.isOlderThanOrEqual(Version.V4_4_4)) {
         return VerifyError.class;
-      }
-      if (version.isNewerThanOrEqual(Version.V7_0_0)) {
+      } else if (version.isNewerThanOrEqual(Version.V7_0_0)) {
         return AbstractMethodError.class;
+      } else {
+        return IncompatibleClassChangeError.class;
+      }
+    } else {
+      if (parameters.getRuntime().asCf().getVm().isGreaterThanOrEqualTo(CfVm.JDK25)) {
+        return VerifyError.class;
+      } else {
+        return IncompatibleClassChangeError.class;
       }
     }
-    return IncompatibleClassChangeError.class;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index e290f65..66362ce 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -120,8 +120,8 @@
       ToolHelper.addProguardConfigurationConsumer(
           builder,
           pgConfig -> {
-            pgConfig.setPrintSeeds(false, null, null);
-            pgConfig.setIgnoreWarnings(true);
+            pgConfig.enablePrintSeeds(null, null, null, null);
+            pgConfig.setIgnoreWarnings(null, null);
           });
       outputApp = new AndroidAppConsumers(builder);
       ToolHelper.runR8(builder.build(), optionsConsumer);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoNotInlineConstructorWithKeptFinalFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoNotInlineConstructorWithKeptFinalFieldTest.java
new file mode 100644
index 0000000..99e7bb6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoNotInlineConstructorWithKeptFinalFieldTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2025, 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.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DoNotInlineConstructorWithKeptFinalFieldTest extends TestBase {
+
+  @Parameter(0)
+  public boolean keep;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, keep: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimesAndAllApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    assumeTrue(parameters.canUseJavaLangInvokeVarHandleStoreStoreFence());
+    assertTrue(parameters.canInitNewInstanceUsingSuperclassConstructor());
+    testForR8(parameters)
+        .addInnerClasses(getClass())
+        // Use most recent android.jar so that VarHandle is present.
+        .applyIf(
+            parameters.isDexRuntime(),
+            testBuilder -> testBuilder.addLibraryFiles(ToolHelper.getMostRecentAndroidJar()))
+        .addKeepMainRule(Main.class)
+        .applyIf(keep, b -> b.addKeepRules("-keepclassmembers class * { final int f; }"))
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // When the field is kept we should not change its modifiers, since the app may, for example,
+    // reflect on whether the final flag is set.
+    FieldSubject fieldSubject = inspector.clazz(Main.class).uniqueFieldWithOriginalName("f");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, onlyIf(keep, isFinal()));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Main main = new Main(args.length);
+      System.out.println(main);
+    }
+
+    final int f;
+
+    Main(int f) {
+      this.f = f;
+    }
+
+    @Override
+    public String toString() {
+      return Integer.toString(f);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerNeverCompileTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerNeverCompileTest.java
new file mode 100644
index 0000000..1d6a31d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerNeverCompileTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2025, 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.ir.optimize.outliner.exceptions;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collection;
+import org.junit.Test;
+
+public class ThrowBlockOutlinerNeverCompileTest extends ThrowBlockOutlinerTestBase {
+
+  @Test
+  public void testD8() throws Exception {
+    runTest(testForD8(parameters), testForD8(parameters));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeRelease();
+    runTest(
+        testForR8(parameters).addKeepMainRule(Main.class).noInliningOfSynthetics(),
+        testForR8(parameters).addKeepMainRule(Main.class).noInliningOfSynthetics());
+  }
+
+  private void runTest(
+      TestCompilerBuilder<?, ?, ?, ? extends SingleTestRunResult<?>, ?> testBuilder,
+      TestCompilerBuilder<?, ?, ?, ? extends SingleTestRunResult<?>, ?> otherTestBuilder)
+      throws Exception {
+    long oatSize =
+        testBuilder
+            .addInnerClasses(getClass())
+            .addOptionsModification(
+                options -> {
+                  assertFalse(options.getThrowBlockOutlinerOptions().neverCompile);
+                })
+            .apply(this::configure)
+            .compile()
+            .inspect(inspector -> inspectOutput(inspector, false))
+            .runDex2Oat(parameters.getRuntime())
+            .getOatSizeOrDefault(-1);
+    assertTrue(0 < oatSize);
+
+    long oatSizeNeverCompile =
+        otherTestBuilder
+            .addInnerClasses(getClass())
+            .addOptionsModification(
+                options -> {
+                  assertFalse(options.getThrowBlockOutlinerOptions().neverCompile);
+                  options.getThrowBlockOutlinerOptions().neverCompile = true;
+                })
+            .apply(this::configure)
+            .compile()
+            .inspect(inspector -> inspectOutput(inspector, true))
+            .runDex2Oat(parameters.getRuntime())
+            .getOatSizeOrDefault(-1);
+    assertTrue(0 < oatSizeNeverCompile);
+    // TODO(b/434769547): Why is the @NeverCompile version not smaller?
+    assertEquals(oatSize, oatSizeNeverCompile);
+  }
+
+  @Override
+  public void inspectOutlines(Collection<ThrowBlockOutline> outlines, DexItemFactory factory) {
+    // Intentionally empty.
+  }
+
+  private void inspectOutput(CodeInspector inspector, boolean neverCompile) {
+    assertEquals(2, inspector.allClasses().size());
+
+    ClassSubject outlineClassSubject =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticThrowBlockOutlineClass(Main.class, 0));
+    assertThat(outlineClassSubject, isPresent());
+    assertEquals(1, outlineClassSubject.allMethods().size());
+
+    MethodSubject outlineMethodSubject = outlineClassSubject.uniqueMethod();
+    assertEquals(neverCompile ? 1 : 0, outlineMethodSubject.annotations().size());
+  }
+
+  @Override
+  public boolean shouldOutline(ThrowBlockOutline outline) {
+    return true;
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      int i = Integer.parseInt(args[0]);
+      if (i == 0) {
+        throw new IllegalArgumentException();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
index abe8855..a982c2d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.unusedarguments;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -45,10 +44,6 @@
   }
 
   private void checkClass(ClassSubject clazz, String expectedAnnotationClass) {
-    if (parameters.canUseJavaLangInvokeVarHandleStoreStoreFence()) {
-      assertThat(clazz, isAbsent());
-      return;
-    }
     assertThat(clazz, isPresent());
     MethodSubject init = clazz.init("Test", "java.lang.String");
     assertThat(init, isPresent());
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
index 42243c0..7fbff7b 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -78,7 +78,7 @@
         ToolHelper.addProguardConfigurationConsumer(
                 R8Command.builder(),
                 pgConfig -> {
-                  pgConfig.enablePrintMapping(null, null);
+                  pgConfig.enablePrintMapping(null, null, null, null);
                   if (!minify.isMinify()) {
                     pgConfig.disableObfuscation();
                   }
diff --git a/src/test/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java b/src/test/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java
index 1ba9022..4be52fb 100644
--- a/src/test/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java
@@ -31,7 +31,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
index 1067960..8f6fb1e 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
@@ -48,8 +48,8 @@
           ToolHelper.addProguardConfigurationConsumer(
                   R8Command.builder(),
                   pgConfig -> {
-                    pgConfig.addRule(ProguardKeepRule.defaultKeepAllRule(unused -> {}));
-                    pgConfig.setRenameSourceFileAttribute(TEST_FILE, null, null);
+                    pgConfig.addRule(ProguardKeepRule.defaultKeepAllRule(unused -> {}), null, null);
+                    pgConfig.setRenameSourceFileAttribute(TEST_FILE, null, null, null);
                     pgConfig.addKeepAttributePatterns(
                         ImmutableList.of("SourceFile", "LineNumberTable"));
                   })
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
index 2f684c0..6ccaba2 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -48,8 +48,8 @@
         ToolHelper.addProguardConfigurationConsumer(
                 ToolHelper.prepareR8CommandBuilder(app),
                 pgConfig -> {
-                  pgConfig.enablePrintMapping(null, null);
-                  pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+                  Path printMappingFile = out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE);
+                  pgConfig.enablePrintMapping(printMappingFile, null, null, null);
                 })
             .addProguardConfiguration(
                 ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/naming/whyareyounotobfuscating/WhyAreYouNotObfuscatingClassTest.java b/src/test/java/com/android/tools/r8/naming/whyareyounotobfuscating/WhyAreYouNotObfuscatingClassTest.java
new file mode 100644
index 0000000..489c552
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/whyareyounotobfuscating/WhyAreYouNotObfuscatingClassTest.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2025, 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.naming.whyareyounotobfuscating;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringDiagnostic;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class WhyAreYouNotObfuscatingClassTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultDexRuntime().withMaximumApiLevel().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-whyareyounotobfuscating class " + Main.class.getTypeName())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(StringDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                Main.class.getTypeName() + " is not obfuscated due to -keep")))));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
index 1807513..f784755 100644
--- a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.OriginMatcher.hasPart;
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.fail;
@@ -15,18 +14,19 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
 import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class ProcessKeepRulesCommandTest extends TestBase {
@@ -37,12 +37,13 @@
           .put("-dontobfuscate", "-dontobfuscate not allowed in library consumer rules.")
           .put("-dontshrink", "-dontshrink not allowed in library consumer rules.")
           .put("-repackageclasses", "-repackageclasses not allowed in library consumer rules.")
-          .put("-printconfiguration", "-printconfiguration not allowed in library consumer rules.")
-          .put("-printmapping", "-printmapping not allowed in library consumer rules.")
           .put("-applymapping foo", "-applymapping not allowed in library consumer rules.")
           .put("-injars foo", "-injars not allowed in library consumer rules.")
           .put("-libraryjars foo", "-libraryjars not allowed in library consumer rules.")
+          .put("-printconfiguration", "-printconfiguration not allowed in library consumer rules.")
+          .put("-printmapping", "-printmapping not allowed in library consumer rules.")
           .put("-printseeds", "-printseeds not allowed in library consumer rules.")
+          .put("-printusage", "-printusage not allowed in library consumer rules.")
           .put(
               "-obfuscationdictionary foo",
               "-obfuscationdictionary not allowed in library consumer rules.")
@@ -77,8 +78,23 @@
               "-keepattributes SourceFile",
               "Illegal attempt to keep the attribute 'SourceFile' in library consumer rules.")
           .put(
+              "-maximumremovedandroidloglevel 2",
+              "-maximumremovedandroidloglevel <int> not allowed in library consumer rules.")
+          .put(
               "-renamesourcefileattribute",
               "-renamesourcefileattribute not allowed in library consumer rules.")
+          .put(
+              "-shrinkunusedprotofields",
+              "-shrinkunusedprotofields not allowed in library consumer rules.")
+          .put(
+              "-whyareyoukeeping class *",
+              "-whyareyoukeeping not allowed in library consumer rules.")
+          .put(
+              "-whyareyounotobfuscating class *",
+              "-whyareyounotobfuscating not allowed in library consumer rules.")
+          .put(
+              "-whyareyounotinlining class * { *; }",
+              "-whyareyounotinlining not allowed in library consumer rules.")
           .build();
 
   @Parameter(1)
@@ -87,32 +103,64 @@
   @Parameter(0)
   public Map.Entry<String, String> configAndExpectedDiagnostic;
 
-  @Parameterized.Parameters(name = "{1}, configAndExpectedDiagnostic = {0}")
+  @Parameters(name = "{1}, configAndExpectedDiagnostic = {0}")
   public static List<Object[]> data() throws IOException {
     return buildParameters(testRules.entrySet(), getTestParameters().withNoneRuntime().build());
   }
 
   @Test
   public void test() throws Exception {
-    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
-    Path tempFile = getStaticTemp().newFile().toPath();
-    Files.write(tempFile, configAndExpectedDiagnostic.getKey().getBytes(StandardCharsets.UTF_8));
-    ProcessKeepRulesCommand command =
-        ProcessKeepRulesCommand.builder(diagnostics)
-            .addKeepRuleFiles(ImmutableList.of(tempFile))
-            .setLibraryConsumerRuleValidation(true)
-            .build();
+    String rules = configAndExpectedDiagnostic.getKey();
+    Origin origin = new PathOrigin(Paths.get("keep.txt"));
     try {
-      ProcessKeepRules.run(command);
+      validate(
+          rules,
+          origin,
+          diagnostics ->
+              diagnostics.assertErrorsMatch(
+                  allOf(
+                      rules.startsWith("-keepattributes")
+                          ? diagnosticType(KeepAttributeLibraryConsumerRuleDiagnostic.class)
+                          : diagnosticType(LibraryConsumerRuleDiagnostic.class),
+                      diagnosticOrigin(equalTo(origin)),
+                      diagnosticMessage(equalTo(configAndExpectedDiagnostic.getValue())))));
       fail("Expect the compilation to fail.");
     } catch (CompilationFailedException e) {
-      diagnostics.assertErrorsMatch(
-          allOf(
-              configAndExpectedDiagnostic.getKey().startsWith("-keepattributes")
-                  ? diagnosticType(KeepAttributeLibraryConsumerRuleDiagnostic.class)
-                  : diagnosticType(GlobalLibraryConsumerRuleDiagnostic.class),
-              diagnosticOrigin(hasPart(tempFile.toString())),
-              diagnosticMessage(equalTo(configAndExpectedDiagnostic.getValue()))));
+      // Expected.
+    }
+
+    // Rerun validation after filtering. This should succeed without diagnostics.
+    validate(filter(rules, origin), origin, TestDiagnosticMessagesImpl::assertNoMessages);
+  }
+
+  private String filter(String rules, Origin origin) throws CompilationFailedException {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    StringBuilder result = new StringBuilder();
+    ProcessKeepRulesCommand command =
+        ProcessKeepRulesCommand.builder(diagnostics)
+            .addKeepRules(rules, origin)
+            .setFilteredKeepRulesConsumer((s, h) -> result.append(s))
+            .build();
+    ProcessKeepRules.run(command);
+    diagnostics.assertNoMessages();
+    return result.toString();
+  }
+
+  private void validate(
+      String rules, Origin origin, Consumer<TestDiagnosticMessagesImpl> diagnosticsInspector)
+      throws CompilationFailedException {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    try {
+      ProcessKeepRulesCommand command =
+          ProcessKeepRulesCommand.builder(diagnostics)
+              .addKeepRules(rules, origin)
+              .setLibraryConsumerRuleValidation(true)
+              .build();
+      ProcessKeepRules.run(command);
+      diagnosticsInspector.accept(diagnostics);
+    } catch (CompilationFailedException e) {
+      diagnosticsInspector.accept(diagnostics);
+      throw e;
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesFilteringTest.java b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesFilteringTest.java
index d834658..bd1cde6 100644
--- a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesFilteringTest.java
+++ b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesFilteringTest.java
@@ -36,10 +36,13 @@
     String keepRules =
         StringUtils.unixLines(
             " -dontobfuscate -dontoptimize -dontshrink -keep class com.example.MainActivity # keep",
-            "# Keep all attributes",
-            "-keepattributes *",
+            "# Keep all annotations",
+            "-keepattributes AnnotationDefault,*Annotations*",
             "# Keep all",
             "-keep  class **",
+            "# Multi line repackageclasses",
+            "-repackageclasses",
+            "    com.example.internal",
             "# End");
     FilteredKeepRules filteredKeepRules = new FilteredKeepRules();
     ProcessKeepRulesCommand command =
@@ -52,10 +55,15 @@
         StringUtils.unixLines(
             " #-dontobfuscate -dontoptimize -dontshrink ",
             "-keep class com.example.MainActivity # keep",
-            "# Keep all attributes",
-            "-keepattributes *",
+            "# Keep all annotations",
+            "#-keepattributes AnnotationDefault,*Annotations*",
+            "-keepattributes"
+                + " AnnotationDefault,RuntimeVisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeVisibleTypeAnnotations",
             "# Keep all",
             "-keep  class **",
+            "# Multi line repackageclasses",
+            "#-repackageclasses",
+            "#    com.example.internal",
             "# End"),
         filteredKeepRules.get());
     diagnostics.assertNoMessages();
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java b/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
index 7227684..649069b 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
@@ -49,7 +49,9 @@
   }
 
   private void inspectRunResult(TestRunResult<?> runResult) {
-    if (parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11)) {
+    if (parameters.isCfRuntime()
+        && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11)
+        && parameters.asCfRuntime().isOlderThan(CfVm.JDK25)) {
       runResult.assertFailureWithErrorThatThrows(AbstractMethodError.class);
     } else {
       runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleMaximallySpecificTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleMaximallySpecificTest.java
index 082d51b..5770963 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleMaximallySpecificTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleMaximallySpecificTest.java
@@ -43,8 +43,10 @@
 @RunWith(Parameterized.class)
 public class MultipleMaximallySpecificTest extends TestBase {
 
-  private static final String EXPECTED_UPTO_JDK9 = StringUtils.lines("A.foo", "Got ICCE");
-  private static final String EXPECTED_AFTER_JDK9 = StringUtils.lines("A.foo", "Got AME");
+  private static final String EXPECTED_UPTO_JDK9_AND_AFTER_25 =
+      StringUtils.lines("A.foo", "Got ICCE");
+  private static final String EXPECTED_AFTER_JDK9_AND_BEFORE_25_AND_ART =
+      StringUtils.lines("A.foo", "Got AME");
 
   private final TestParameters parameters;
 
@@ -118,22 +120,18 @@
     }
   }
 
-  private boolean isDesugaring() {
-    return parameters.isDexRuntime()
-        && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
-  }
-
-  private boolean isNewCfRuntime() {
-    return parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThan(CfVm.JDK9);
-  }
-
   @Test
   public void testRuntime() throws Exception {
     testForRuntime(parameters)
         .addProgramClasses(getInputClasses())
         .addProgramClassFileData(getTransformedClasses())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(isNewCfRuntime() ? EXPECTED_AFTER_JDK9 : EXPECTED_UPTO_JDK9);
+        .assertSuccessWithOutput(
+            parameters.isCfRuntime()
+                    && parameters.asCfRuntime().isNewerThan(CfVm.JDK9)
+                    && parameters.asCfRuntime().isOlderThan(CfVm.JDK25)
+                ? EXPECTED_AFTER_JDK9_AND_BEFORE_25_AND_ART
+                : EXPECTED_UPTO_JDK9_AND_AFTER_25);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
index 9dd9eb6..075edcd 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
@@ -130,29 +130,30 @@
         containsString(
             "-printconfiguration "
                 + (ToolHelper.isWindows()
-                    ? ("'" + proguardConfigOutFile.toAbsolutePath().toString() + "'")
+                    ? ("'" + proguardConfigOutFile.toAbsolutePath() + "'")
                     : proguardConfigOutFile.toAbsolutePath().toString())));
   }
 
   @Test
   public void testIncludeFile() throws Exception {
-    Class mainClass = PrintConfigurationTestClass.class;
+    Class<?> mainClass = PrintConfigurationTestClass.class;
     String includeProguardConfig = keepMainProguardConfiguration(mainClass);
     Path includeFile = temp.newFile().toPath();
     FileUtils.writeTextFile(includeFile, includeProguardConfig);
     Path printConfigurationFile = temp.newFile().toPath();
-    String proguardConfig = String.join(System.lineSeparator(), ImmutableList.of(
-        "-include " + includeFile.toString(),
-        "-printconfiguration " + printConfigurationFile.toString()
-    ));
+    String proguardConfig =
+        String.join(
+            System.lineSeparator(),
+            ImmutableList.of(
+                "-include " + includeFile, "-printconfiguration " + printConfigurationFile));
 
-    String expected = String.join(System.lineSeparator(), ImmutableList.of(
-        "",  // The -include line turns into an empty line.
-        includeProguardConfig,
-        "",  // Writing to the file adds an ending line separator
-        "",  // An empty line is emitted between two parts
-        "-printconfiguration " + printConfigurationFile.toString()
-    ));
+    String expected =
+        StringUtils.joinLines(
+            "-printconfiguration " + printConfigurationFile,
+            "", // The -include line turns into an empty line.
+            includeProguardConfig,
+            "", // Writing to the file adds an ending line separator
+            ""); // An empty line is emitted between two parts
     compileWithR8(ImmutableList.of(mainClass), proguardConfig);
     assertEqualsStripOrigin(
         expected, FileUtils.readTextFile(printConfigurationFile, Charsets.UTF_8));
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index cb788dc..5d1c257 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -184,7 +184,6 @@
             reporter,
             ProguardConfigurationParserOptions.builder()
                 .setEnableLegacyFullModeForKeepRules(false)
-                .setEnableExperimentalCheckEnumUnboxed(false)
                 .setEnableTestingOptions(false)
                 .build(),
             null,
@@ -202,7 +201,6 @@
             dexItemFactory,
             reporter,
             ProguardConfigurationParserOptions.builder()
-                .setEnableExperimentalCheckEnumUnboxed(false)
                 .setEnableTestingOptions(true)
                 .build(),
             null,
@@ -723,7 +721,6 @@
             dexItemFactory,
             reporter,
             ProguardConfigurationParserOptions.builder()
-                .setEnableExperimentalCheckEnumUnboxed(false)
                 .setEnableTestingOptions(false)
                 .build(),
             null,
@@ -743,7 +740,6 @@
             dexItemFactory,
             reporter,
             ProguardConfigurationParserOptions.builder()
-                .setEnableExperimentalCheckEnumUnboxed(false)
                 .setEnableTestingOptions(false)
                 .build(),
             null,
@@ -861,7 +857,7 @@
     verifyParserEndsCleanly();
     ProguardConfiguration config = builder.build();
     assertEquals(
-        "-keepattributes RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations",
+        "-keepattributes RuntimeInvisibleAnnotations,RuntimeVisibleAnnotations",
         config.getKeepAttributes().toString());
     assertEquals(
         StringUtils.joinLines("-keep class kotlin.Metadata {", "  *;", "}"),
@@ -881,7 +877,7 @@
       parser.parse(path);
       fail();
     } catch (RuntimeException e) {
-      checkDiagnostics(handler.errors, path, 6, 10,"does-not-exist.flags");
+      checkDiagnostics(handler.errors, path, 6, 1, "does-not-exist.flags");
     }
   }
 
@@ -892,7 +888,7 @@
       parser.parse(path);
       fail();
     } catch (RuntimeException e) {
-      checkDiagnostics(handler.errors, path, 6,2, "does-not-exist.flags");
+      checkDiagnostics(handler.errors, path, 6, 1, "does-not-exist.flags");
     }
   }
 
@@ -3023,4 +3019,48 @@
       assertEquals(MaximumRemovedAndroidLogLevelRule.VERBOSE, rule.getMaxRemovedAndroidLogLevel());
     }
   }
+
+  @Test
+  public void testParsedConfigurationWithInclude() throws Exception {
+    Path config = temp.newFile("config.txt").toPath().toAbsolutePath();
+    Path include1 = temp.newFile("include1.txt").toPath().toAbsolutePath();
+    Path include11 = temp.newFile("include1_1.txt").toPath().toAbsolutePath();
+    Path include2 = temp.newFile("include2.txt").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(
+        config,
+        StringUtils.joinLines(
+            "# Before Include 1",
+            "-include " + include1,
+            "# After Include 1",
+            "-include " + include2,
+            "# After Include 2"));
+    FileUtils.writeTextFile(
+        include1, StringUtils.joinLines("# Include 1", "-include " + include11));
+    FileUtils.writeTextFile(include11, "# Include 1.1");
+    FileUtils.writeTextFile(include2, "# Include 2");
+    parser.parse(config);
+    verifyParserEndsCleanly();
+    String parsedConfiguration = builder.build().getParsedConfiguration();
+    String separator = ToolHelper.isWindows() ? "\\" : "/";
+    assertEquals(
+        StringUtils.lines(
+            "# The proguard configuration file for the following section is config.txt",
+            "# Before Include 1",
+            "", // -include include1.txt
+            "# After Include 1",
+            "", // -include include2.txt
+            "# After Include 2",
+            "# End of content from config.txt",
+            "# The proguard configuration file for the following section is include1.txt",
+            "# Include 1",
+            "", // -include include1_1.txt.
+            "# End of content from include1.txt",
+            "# The proguard configuration file for the following section is include1_1.txt",
+            "# Include 1.1",
+            "# End of content from include1_1.txt",
+            "# The proguard configuration file for the following section is include2.txt",
+            "# Include 2",
+            "# End of content from include2.txt"),
+        StringUtils.replaceAll(parsedConfiguration, temp.getRoot().toString() + separator, ""));
+  }
 }
\ No newline at end of file
diff --git a/src/test/java17/com/android/tools/r8/jdk17/records/RecordComponentAnnotationsTest.java b/src/test/java17/com/android/tools/r8/jdk17/records/RecordComponentAnnotationsTest.java
index 17666e8..524207d 100644
--- a/src/test/java17/com/android/tools/r8/jdk17/records/RecordComponentAnnotationsTest.java
+++ b/src/test/java17/com/android/tools/r8/jdk17/records/RecordComponentAnnotationsTest.java
@@ -222,7 +222,8 @@
         .addInnerClassesAndStrippedOuter(getClass())
         .run(parameters.getRuntime(), RecordWithAnnotations.class)
         .assertSuccessWithOutput(
-            parameters.getRuntime().asCf().getVm().isLessThanOrEqualTo(CfVm.JDK20)
+            // Result changed from JDK-20 to JDK-21, but we only test LTS, so check for JDK-17 here.
+            parameters.getRuntime().asCf().getVm().isLessThanOrEqualTo(CfVm.JDK17)
                 ? JVM_UNTIL_20_EXPECTED_RESULT
                 : JVM_FROM_21_EXPECTED_RESULT);
   }
diff --git a/src/test/java21/com/android/tools/r8/jdk21/assistant/JavaLangClass21JsonTest.java b/src/test/java21/com/android/tools/r8/jdk21/assistant/JavaLangClass21JsonTest.java
new file mode 100644
index 0000000..3f682f7
--- /dev/null
+++ b/src/test/java21/com/android/tools/r8/jdk21/assistant/JavaLangClass21JsonTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2025, 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.jdk21.assistant;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.assistant.postprocessing.ReflectiveOperationJsonParser;
+import com.android.tools.r8.assistant.postprocessing.model.ClassFlagEvent;
+import com.android.tools.r8.assistant.postprocessing.model.ReflectiveEvent;
+import com.android.tools.r8.assistant.runtime.ReflectiveEventType;
+import com.android.tools.r8.assistant.runtime.ReflectiveOperationJsonLogger;
+import com.android.tools.r8.assistant.runtime.ReflectiveOperationReceiver.ClassFlag;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.jdk21.assistant.JavaLangTestClass21.Foo;
+import com.android.tools.r8.utils.Box;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class JavaLangClass21JsonTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNativeMultidexDexRuntimes().withMaximumApiLevel().build();
+  }
+
+  @Test
+  public void testInstrumentationWithCustomOracle() throws Exception {
+    Path path = Paths.get(temp.newFile().getAbsolutePath());
+    Box<DexItemFactory> factoryBox = new Box<>();
+    testForAssistant()
+        .addProgramClasses(JavaLangTestClass21.class, Foo.class)
+        .addStrippedOuter(getClass())
+        .addInstrumentationClasses(Instrumentation.class)
+        .setCustomReflectiveOperationReceiver(Instrumentation.class)
+        .setMinApi(parameters)
+        .addOptionsModification(opt -> factoryBox.set(opt.itemFactory))
+        .compile()
+        .addVmArguments("-Dcom.android.tools.r8.reflectiveJsonLogger=" + path)
+        .run(parameters.getRuntime(), JavaLangTestClass21.class)
+        .assertSuccess();
+    List<ReflectiveEvent> reflectiveEvents =
+        new ReflectiveOperationJsonParser(factoryBox.get()).parse(path);
+    assertEquals(14, reflectiveEvents.size());
+
+    ClassFlag[] expectedClassFlags = {
+      ClassFlag.ANNOTATION,
+      ClassFlag.ANONYMOUS_CLASS,
+      ClassFlag.ARRAY,
+      ClassFlag.ENUM,
+      ClassFlag.HIDDEN,
+      ClassFlag.INTERFACE,
+      ClassFlag.LOCAL_CLASS,
+      ClassFlag.MEMBER_CLASS,
+      ClassFlag.PRIMITIVE,
+      ClassFlag.RECORD,
+      ClassFlag.SEALED,
+      ClassFlag.SYNTHETIC
+    };
+
+    for (int i = 2; i < reflectiveEvents.size(); i++) {
+      ReflectiveEvent event = reflectiveEvents.get(i);
+      ClassFlagEvent classFlagEvent = event.asClassFlagEvent();
+      assertEquals(ReflectiveEventType.CLASS_FLAG, classFlagEvent.getEventType());
+      assertEquals(expectedClassFlags[i - 2], classFlagEvent.getClassFlag());
+    }
+  }
+
+  public static class Instrumentation extends ReflectiveOperationJsonLogger {
+
+    public Instrumentation() throws IOException {}
+  }
+}
diff --git a/src/test/java21/com/android/tools/r8/jdk21/jdk8272564/Jdk8272564Test.java b/src/test/java21/com/android/tools/r8/jdk21/jdk8272564/Jdk8272564Test.java
index 55b4111..4d20227 100644
--- a/src/test/java21/com/android/tools/r8/jdk21/jdk8272564/Jdk8272564Test.java
+++ b/src/test/java21/com/android/tools/r8/jdk21/jdk8272564/Jdk8272564Test.java
@@ -30,7 +30,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK20)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK21)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/Java24ValidationTest.java b/src/test/java25/com/android/tools/r8/jdk25/Java25ValidationTest.java
similarity index 90%
rename from src/test/java24/com/android/tools/r8/jdk24/Java24ValidationTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/Java25ValidationTest.java
index 05ef994..65ac630 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/Java24ValidationTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/Java25ValidationTest.java
@@ -2,7 +2,7 @@
 // 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.jdk24;
+package com.android.tools.r8.jdk25;
 
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 import static junit.framework.TestCase.assertEquals;
@@ -24,9 +24,9 @@
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 
-// Test to validate that the tests_java_23 module is built with JDK-23.
+// Test to validate that the tests_java_25 module is built with JDK-25.
 @RunWith(Parameterized.class)
-public class Java24ValidationTest extends TestBase {
+public class Java25ValidationTest extends TestBase {
 
   static final String EXPECTED = StringUtils.lines("Hello, world");
 
@@ -37,7 +37,7 @@
     return getTestParameters().withCfRuntimes().build();
   }
 
-  public Java24ValidationTest(TestParameters parameters) {
+  public Java25ValidationTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -88,7 +88,7 @@
         .addInnerClasses(getClass())
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
-            parameters.getCfRuntime().isOlderThan(CfVm.JDK24),
+            parameters.getCfRuntime().isOlderThan(CfVm.JDK25),
             r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class),
             r -> r.assertSuccessWithOutput(EXPECTED));
   }
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/DexNumberValueSwitchTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/DexNumberValueSwitchTest.java
similarity index 97%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/DexNumberValueSwitchTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/DexNumberValueSwitchTest.java
index 9221d84..9f9d2c1 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/DexNumberValueSwitchTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/DexNumberValueSwitchTest.java
@@ -2,7 +2,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
@@ -30,7 +30,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/DexValueSwitchTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/DexValueSwitchTest.java
similarity index 98%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/DexValueSwitchTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/DexValueSwitchTest.java
index 0bb2ee9..633b792 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/DexValueSwitchTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/DexValueSwitchTest.java
@@ -2,7 +2,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
@@ -30,7 +30,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
similarity index 97%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
index e1f549a..b24f759 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
@@ -1,7 +1,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
@@ -34,7 +34,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
similarity index 98%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
index 980d8d4..578bc5f 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
@@ -1,7 +1,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.desugarMatchException;
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
@@ -36,7 +36,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchOldSyntaxTest.java
similarity index 96%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchOldSyntaxTest.java
index 5774937..95afb4f 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchOldSyntaxTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2025 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -27,7 +27,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
similarity index 96%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
index fede4cb..30fc275 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2025 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static org.junit.Assert.assertEquals;
 
@@ -26,7 +26,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchTest.java
similarity index 96%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchTest.java
index 8ba7a03..b2063b7 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchTest.java
@@ -1,7 +1,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.desugarMatchException;
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
@@ -35,7 +35,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
@@ -61,7 +61,7 @@
 
   private <T extends TestBuilder<?, T>> void addModifiedProgramClasses(
       TestBuilder<?, T> testBuilder) throws Exception {
-    String d = "com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchTest$D";
+    String d = "com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchTest$D";
     testBuilder
         .addStrippedOuter(getClass())
         .addProgramClasses(FakeI.class, E.class, C.class)
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
similarity index 97%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
index 6e1e2b0..4a30ba3 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
@@ -1,7 +1,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
 import static org.junit.Assert.assertTrue;
@@ -32,7 +32,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/StringSwitchOldSyntaxTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/StringSwitchOldSyntaxTest.java
similarity index 95%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/StringSwitchOldSyntaxTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/StringSwitchOldSyntaxTest.java
index b83c3e5..c64ea5e 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/StringSwitchOldSyntaxTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/StringSwitchOldSyntaxTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2025, 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
@@ -23,7 +23,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/StringSwitchRegress382880986Test.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/StringSwitchRegress382880986Test.java
similarity index 96%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/StringSwitchRegress382880986Test.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/StringSwitchRegress382880986Test.java
index eb9f60b..6ad8e58 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/StringSwitchRegress382880986Test.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/StringSwitchRegress382880986Test.java
@@ -1,7 +1,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
@@ -29,7 +29,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/StringSwitchTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/StringSwitchTest.java
similarity index 96%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/StringSwitchTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/StringSwitchTest.java
index 3387976..219391c 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/StringSwitchTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/StringSwitchTest.java
@@ -1,7 +1,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
@@ -32,7 +32,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchEnumAsClassTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/TypeSwitchEnumAsClassTest.java
similarity index 96%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchEnumAsClassTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/TypeSwitchEnumAsClassTest.java
index 1e99ca6..df14fe2 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchEnumAsClassTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/TypeSwitchEnumAsClassTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2025, 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
@@ -32,7 +32,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchMissingClassTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/TypeSwitchMissingClassTest.java
similarity index 97%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchMissingClassTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/TypeSwitchMissingClassTest.java
index dcf4812..175bc9b 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchMissingClassTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/TypeSwitchMissingClassTest.java
@@ -1,7 +1,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
@@ -37,7 +37,7 @@
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters()
-            .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build(),
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchTest.java b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/TypeSwitchTest.java
similarity index 96%
rename from src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchTest.java
rename to src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/TypeSwitchTest.java
index 97067ee..8258d20 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchTest.java
+++ b/src/test/java25/com/android/tools/r8/jdk25/switchpatternmatching/TypeSwitchTest.java
@@ -1,7 +1,7 @@
 // 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.jdk24.switchpatternmatching;
+package com.android.tools.r8.jdk25.switchpatternmatching;
 
 import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
@@ -32,7 +32,7 @@
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK25)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .withPartialCompilation()
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
index 58f7a23..b5588f1 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
@@ -622,8 +622,7 @@
 
   public T enableCheckEnumUnboxedAnnotations() {
     return addCheckEnumUnboxedAnnotation()
-        .addInternalMatchInterfaceRule(CheckEnumUnboxedRule.RULE_NAME, CheckEnumUnboxed.class)
-        .enableExperimentalCheckEnumUnboxed();
+        .addInternalMatchInterfaceRule(CheckEnumUnboxedRule.RULE_NAME, CheckEnumUnboxed.class);
   }
 
   public T enableKeepUnusedReturnValueAnnotations() {
@@ -813,11 +812,6 @@
     return self();
   }
 
-  public T enableExperimentalCheckEnumUnboxed() {
-    builder.setEnableExperimentalCheckEnumUnboxed();
-    return self();
-  }
-
   public T enableExperimentalKeepAnnotations(KeepAnnotationLibrary keepAnnotationLibrary) {
     return addOptionsModification(o -> o.testing.enableEmbeddedKeepAnnotations = true)
         .addKeepAnnoLibToClasspath(keepAnnotationLibrary);
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java
index 31158a3..90c65bd 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -948,7 +948,7 @@
           factory -> {
             ProguardConfiguration.Builder builder =
                 ProguardConfiguration.builder(factory, new Reporter());
-            builder.addRule(ProguardKeepRule.defaultKeepAllRule(unused -> {}));
+            builder.addRule(ProguardKeepRule.defaultKeepAllRule(unused -> {}), null, null);
             return builder
                 .addKeepAttributePatterns(ImmutableList.of(ProguardKeepAttributes.SIGNATURE))
                 .build();
@@ -1095,7 +1095,7 @@
   protected static ProguardConfiguration buildConfigForRules(
       DexItemFactory factory, Reporter reporter, Collection<ProguardConfigurationRule> rules) {
     ProguardConfiguration.Builder builder = ProguardConfiguration.builder(factory, reporter);
-    rules.forEach(builder::addRule);
+    rules.forEach(rule -> builder.addRule(rule, null, null));
     return builder.build();
   }
 
diff --git a/src/test/testbase/java/com/android/tools/r8/TestRuntime.java b/src/test/testbase/java/com/android/tools/r8/TestRuntime.java
index 84059ff..01a80bc 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestRuntime.java
@@ -34,15 +34,11 @@
     JDK8("jdk8", 52),
     JDK9("jdk9", 53),
     JDK10("jdk10", 54),
+    // From JDK-11 only include LTS and latest non-LTS.
     JDK11("jdk11", 55),
     JDK17("jdk17", 61),
-    JDK18("jdk18", 62),
-    JDK20("jdk20", 64),
-    // From JDK-21 only include LTS and latest non-LTS.
     JDK21("jdk21", 65),
-    // TODO(b/383073689) Remove JDK-23 when bots test JDK-23.
-    JDK23("jdk23", 67),
-    JDK24("jdk24", 68);
+    JDK25("jdk25", 69); // Keep JDK-25 (LTS) when adding JDK-26.
 
     /** This should generally be the latest checked in CF runtime we fully support. */
     private static final CfVm DEFAULT = JDK11;
@@ -101,8 +97,7 @@
   private static final Path JDK11_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-11");
   private static final Path JDK17_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-17");
   private static final Path JDK21_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-21");
-  private static final Path JDK23_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-23");
-  private static final Path JDK24_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-24");
+  private static final Path JDK25_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-25");
   private static final Map<CfVm, Path> jdkPaths =
       ImmutableMap.of(
           CfVm.JDK8, JDK8_PATH,
@@ -110,8 +105,7 @@
           CfVm.JDK11, JDK11_PATH,
           CfVm.JDK17, JDK17_PATH,
           CfVm.JDK21, JDK21_PATH,
-          CfVm.JDK23, JDK23_PATH,
-          CfVm.JDK24, JDK24_PATH);
+          CfVm.JDK25, JDK25_PATH);
 
   public static CfRuntime getCheckedInJdk(CfVm vm) {
     if (vm == CfVm.JDK8) {
@@ -164,8 +158,8 @@
     return new CfRuntime(CfVm.JDK21, getCheckedInJdkHome(CfVm.JDK21));
   }
 
-  public static CfRuntime getCheckedInJdk24() {
-    return new CfRuntime(CfVm.JDK24, getCheckedInJdkHome(CfVm.JDK24));
+  public static CfRuntime getCheckedInJdk25() {
+    return new CfRuntime(CfVm.JDK25, getCheckedInJdkHome(CfVm.JDK25));
   }
 
   public static List<CfRuntime> getCheckedInCfRuntimes() {
@@ -176,7 +170,7 @@
           getCheckedInJdk11(),
           getCheckedInJdk17(),
           getCheckedInJdk21(),
-          getCheckedInJdk24()
+          getCheckedInJdk25()
         };
     Builder<CfRuntime> builder = ImmutableList.builder();
     for (CfRuntime jdk : jdks) {
@@ -237,8 +231,8 @@
     if (version.equals("21") || version.startsWith("21.")) {
       return new CfRuntime(CfVm.JDK21, Paths.get(home));
     }
-    if (version.equals("24") || version.startsWith("24.")) {
-      return new CfRuntime(CfVm.JDK24, Paths.get(home));
+    if (version.equals("25") || version.startsWith("25.")) {
+      return new CfRuntime(CfVm.JDK25, Paths.get(home));
     }
     throw new Unimplemented("No support for JDK version: " + version);
   }
diff --git a/third_party/openjdk/jdk-25/linux.tar.gz.sha1 b/third_party/openjdk/jdk-25/linux.tar.gz.sha1
new file mode 100644
index 0000000..d91d054
--- /dev/null
+++ b/third_party/openjdk/jdk-25/linux.tar.gz.sha1
@@ -0,0 +1 @@
+9e335e4adb9d23d93cd8f3d47205d4d4aa6f23fa
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-25/osx.tar.gz.sha1 b/third_party/openjdk/jdk-25/osx.tar.gz.sha1
new file mode 100644
index 0000000..644b0f9
--- /dev/null
+++ b/third_party/openjdk/jdk-25/osx.tar.gz.sha1
@@ -0,0 +1 @@
+46fcab6f0c50aca73b45cb2e3484f91ec7a0d667
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-25/windows.tar.gz.sha1 b/third_party/openjdk/jdk-25/windows.tar.gz.sha1
new file mode 100644
index 0000000..bec4649
--- /dev/null
+++ b/third_party/openjdk/jdk-25/windows.tar.gz.sha1
@@ -0,0 +1 @@
+adcf71c4772ec35c4a9c00512e0a5fdfc53b02f0
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_17.19.tar.gz.sha1 b/third_party/youtube/youtube.android_17.19.tar.gz.sha1
index e672542..fbf7aa7 100644
--- a/third_party/youtube/youtube.android_17.19.tar.gz.sha1
+++ b/third_party/youtube/youtube.android_17.19.tar.gz.sha1
@@ -1 +1 @@
-474aefd92152017a7e0ad54020b95c6d5045a0bb
\ No newline at end of file
+741cbc7f6f1a80956e0ff6d25b43242e21d8225a
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 1a9dd57..25e42ec 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -154,9 +154,9 @@
 
 
 def GetJavaEnv(androidHomeTemp):
-    java_env = dict(os.environ, JAVA_HOME=jdk.GetJdk11Home())
+    java_env = dict(os.environ, JAVA_HOME=jdk.GetJdk17Home())
     java_env['PATH'] = java_env['PATH'] + os.pathsep + os.path.join(
-        jdk.GetJdk11Home(), 'bin')
+        jdk.GetJdk17Home(), 'bin')
     java_env['GRADLE_OPTS'] = '-Xmx1g'
     java_env['ANDROID_HOME'] = androidHomeTemp
     return java_env
@@ -392,7 +392,7 @@
     # Make sure bazel is extracted in third_party.
     utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
     utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
-    utils.DownloadFromGoogleCloudStorage(utils.JAVA11_SHA_FILE)
+    utils.DownloadFromGoogleCloudStorage(utils.JAVA17_SHA_FILE)
     utils.DownloadFromGoogleCloudStorage(utils.DESUGAR_JDK_LIBS_11_SHA_FILE)
 
     for v in options.variant:
diff --git a/tools/jdk.py b/tools/jdk.py
index e2454eb..bff5a96 100755
--- a/tools/jdk.py
+++ b/tools/jdk.py
@@ -10,7 +10,7 @@
 
 JDK_DIRS = os.path.join(defines.THIRD_PARTY, 'openjdk')
 
-ALL_JDKS = ['openjdk-9.0.4', 'jdk-11', 'jdk-17', 'jdk-21', 'jdk-24']
+ALL_JDKS = ['openjdk-9.0.4', 'jdk-11', 'jdk-17', 'jdk-21', 'jdk-25']
 
 
 def GetDefaultJdkHome():
@@ -62,6 +62,10 @@
     return dirs
 
 
+def GetJdk17Home():
+    return GetJdkHome('jdk-17')
+
+
 def GetJdk11Home():
     return GetJdkHome('jdk-11')
 
diff --git a/tools/test.py b/tools/test.py
index 811c95e..3ed2c71 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -45,7 +45,7 @@
 NUMBER_OF_TEST_REPORTS = 5
 REPORTS_PATH = os.path.join(utils.BUILD, 'reports')
 REPORT_INDEX = ['tests', 'test', 'index.html']
-VALID_RUNTIMES = ['none', 'jdk8', 'jdk9', 'jdk11', 'jdk17', 'jdk21', 'jdk24'
+VALID_RUNTIMES = ['none', 'jdk8', 'jdk9', 'jdk11', 'jdk17', 'jdk21', 'jdk25'
                  ] + ['dex-%s' % dexvm for dexvm in ALL_ART_VMS]
 
 
diff --git a/tools/utils.py b/tools/utils.py
index 23453aa..29ecb79 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -146,6 +146,8 @@
                               'linux-x86.tar.gz.sha1')
 JAVA11_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk-11',
                                'linux.tar.gz.sha1')
+JAVA17_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk-17',
+                               'linux.tar.gz.sha1')
 DESUGAR_JDK_LIBS_11_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk',
                                             'desugar_jdk_libs_11.tar.gz.sha1')
 IGNORE_WARNINGS_RULES = os.path.join(REPO_ROOT, 'src', 'test',