Merge commit 'b02372be4e2142e2ae4922edfe4ffb1953bca294' into dev-release
diff --git a/.gitignore b/.gitignore
index b19c671..bd447f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,8 @@
 third_party/android_jar/lib-v[0-9][0-9]
 third_party/android_jar/lib-v[0-9][0-9].tar.gz
 third_party/android_jar/lib.tar.gz
+third_party/android_jar/libcore_latest
+third_party/android_jar/libcore_latest.tar.gz
 third_party/android_jar/api-versions.tar.gz
 third_party/android_jar/api-versions
 third_party/android_sdk
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 088b42f..87fd416 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -15,6 +15,9 @@
     'scripts',
     'google-java-format-diff.py')
 
+FMT_CMD_JDK17 = path.join('tools','google-java-format-diff.py')
+
+
 def CheckDoNotMerge(input_api, output_api):
   for l in input_api.change.FullDescriptionText().splitlines():
     if l.lower().startswith('do not merge'):
@@ -47,7 +50,17 @@
 or bypass the checks with:
 
   git cl upload --bypass-hooks
-  """ % (FMT_CMD, FMT_CMD)))
+
+If formatting fails with 'No enum constant javax.lang.model.element.Modifier.SEALED' try
+
+  git diff -U0 $(git cl upstream) | %s %s %s -p1 -i && git commit -a --amend --no-edit && git cl upload
+  """ % (
+    FMT_CMD,
+    FMT_CMD,
+    FMT_CMD_JDK17,
+    '--google-java-format-jar',
+    'third_party/google/google-java-format/1.14.0/google-java-format-1.14.0-all-deps.jar'
+)))
   return results
 
 def CheckDeterministicDebuggingChanged(input_api, output_api, branch):
diff --git a/build.gradle b/build.gradle
index 21a20be..9b2772c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -303,6 +303,7 @@
         ],
         "third_party": [
                 "android_cts_baseline",
+                "android_jar/libcore_latest",
                 "android_jar/lib-v14",
                 "android_jar/lib-v15",
                 "android_jar/lib-v19",
@@ -460,7 +461,8 @@
         "protobuf-lite",
         "retrace_internal",
         "youtube/youtube.android_15.33",
-        "youtube/youtube.android_16.20"
+        "youtube/youtube.android_16.20",
+        "youtube/youtube.android_17.19"
     ],
 ]
 
@@ -2274,6 +2276,9 @@
     if (!project.hasProperty('all_tests')) {
         exclude "com/android/tools/r8/art/dx/**"
     }
+    if (project.hasProperty('no_arttests')) {
+        exclude "com/android/tools/r8/art/**"
+    }
     if (project.hasProperty('shard_count') ) {
       assert project.hasProperty('shard_number')
       int shard_count = project.getProperty('shard_count') as Integer
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 0323612..ca4bc25 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -97,6 +97,7 @@
         '    "--one_line_per_test",'
         '    "--archive_failures",'
         '    "--no_internal",'
+        '    "--no_arttests",'
         '    "--desugared-library",'
         '    "HEAD"'
         '  ]'
@@ -132,6 +133,7 @@
         '    "--one_line_per_test",'
         '    "--archive_failures",'
         '    "--no_internal",'
+        '    "--no_arttests",'
         '    "--desugared-library",'
         '    "HEAD",'
         '    "--desugared-library-configuration",'
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg
index 6153ae8..53f90a7 100644
--- a/infra/config/global/generated/project.cfg
+++ b/infra/config/global/generated/project.cfg
@@ -7,7 +7,7 @@
 name: "r8"
 access: "group:all"
 lucicfg {
-  version: "1.30.10"
+  version: "1.30.11"
   package_dir: ".."
   config_dir: "generated"
   entry_point: "main.star"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 4949fc2..d1529cf 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -342,6 +342,7 @@
         "--one_line_per_test",
         "--archive_failures",
         "--no_internal",
+        "--no_arttests",
         "--desugared-library",
         "HEAD"
     ]
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 9393f4d..9a340b8 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -15,12 +15,14 @@
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
@@ -34,6 +36,7 @@
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticFinalization;
 import com.android.tools.r8.synthesis.SyntheticItems;
@@ -211,6 +214,9 @@
       timing.begin("Read input app");
       AppView<AppInfo> appView = readApp(inputApp, options, executor, timing);
       timing.end();
+      timing.begin("Initialize assume info collection");
+      initializeAssumeInfoCollection(appView);
+      timing.end();
       timing.begin("Desugared library amend");
       DesugaredLibraryAmender.run(appView);
       timing.end();
@@ -343,6 +349,22 @@
     }
   }
 
+  private static void initializeAssumeInfoCollection(AppView<AppInfo> appView) {
+    AssumeInfoCollection.Builder assumeInfoCollectionBuilder = AssumeInfoCollection.builder();
+    AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    InternalOptions options = appView.options();
+    if (options.isGeneratingDex()) {
+      assumeInfoCollectionBuilder
+          .meetAssumeValue(
+              dexItemFactory.androidOsBuildVersionMembers.SDK_INT,
+              abstractValueFactory.createNumberFromIntervalValue(
+                  options.getMinApiLevel().getLevel(), Integer.MAX_VALUE))
+          .setIsSideEffectFree(dexItemFactory.androidOsBuildVersionMembers.SDK_INT);
+    }
+    appView.setAssumeInfoCollection(assumeInfoCollectionBuilder.build());
+  }
+
   private static void finalizeApplication(AppView<AppInfo> appView, ExecutorService executorService)
       throws ExecutionException {
     SyntheticFinalization.finalize(appView, executorService);
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 9dc5143..af340d4 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -205,6 +205,7 @@
             null,
             null,
             Collections.emptyList(),
+            Collections.emptyList(),
             null,
             Collections.emptyList(),
             ClassSignature.noSignature(),
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5d07a1c..d70426f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -79,6 +79,7 @@
 import com.android.tools.r8.shaking.AbstractMethodRemover;
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
 import com.android.tools.r8.shaking.ClassInitFieldSynthesizer;
 import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
 import com.android.tools.r8.shaking.DiscardedChecker;
@@ -338,6 +339,7 @@
                     options.itemFactory, options.getMinApiLevel()));
           }
         }
+        AssumeInfoCollection.Builder assumeInfoCollectionBuilder = AssumeInfoCollection.builder();
         SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
         appView.setRootSet(
             RootSet.builder(
@@ -345,7 +347,9 @@
                     subtypingInfo,
                     Iterables.concat(
                         options.getProguardConfiguration().getRules(), synthesizedProguardRules))
+                .setAssumeInfoCollectionBuilder(assumeInfoCollectionBuilder)
                 .build(executorService));
+        appView.setAssumeInfoCollection(assumeInfoCollectionBuilder.build());
 
         // Compute the main dex rootset that will be the base of first and final main dex tracing
         // before building a new appview with only live classes (and invalidating subtypingInfo).
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4e7ca32..8d323ab 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationParserOptions;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationSource;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceBytes;
@@ -116,8 +117,8 @@
     private boolean skipDump = false;
     private boolean enableMissingLibraryApiModeling = false;
 
-    private boolean allowTestProguardOptions =
-        System.getProperty("com.android.tools.r8.allowTestProguardOptions") != null;
+    private final ProguardConfigurationParserOptions.Builder parserOptionsBuilder =
+        ProguardConfigurationParserOptions.builder().readEnvironment();
 
     // TODO(zerny): Consider refactoring CompatProguardCommandBuilder to avoid subclassing.
     Builder() {
@@ -520,7 +521,7 @@
 
       ProguardConfigurationParser parser =
           new ProguardConfigurationParser(
-              factory, reporter, inputDependencyGraphConsumer, allowTestProguardOptions);
+              factory, reporter, parserOptionsBuilder.build(), inputDependencyGraphConsumer);
       if (!proguardConfigs.isEmpty()) {
         parser.parse(proguardConfigs);
       }
@@ -662,9 +663,21 @@
 
     }
 
+    void setEnableExperimentalCheckEnumUnboxed() {
+      parserOptionsBuilder.setEnableExperimentalCheckEnumUnboxed(true);
+    }
+
+    void setEnableExperimentalConvertCheckNotNull() {
+      parserOptionsBuilder.setEnableExperimentalConvertCheckNotNull(true);
+    }
+
+    void setEnableExperimentalWhyAreYouNotInlining() {
+      parserOptionsBuilder.setEnableExperimentalWhyAreYouNotInlining(true);
+    }
+
     // Internal for-testing method to allow proguard options only available for testing.
-    void allowTestProguardOptions() {
-      allowTestProguardOptions = true;
+    void setEnableTestProguardOptions() {
+      parserOptionsBuilder.setEnableTestingOptions(true);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/androidapi/CovariantReturnTypeMethods.java b/src/main/java/com/android/tools/r8/androidapi/CovariantReturnTypeMethods.java
new file mode 100644
index 0000000..077c1be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/CovariantReturnTypeMethods.java
@@ -0,0 +1,289 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateCovariantReturnTypeMethodsTest.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.androidapi;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import java.util.function.Consumer;
+
+public class CovariantReturnTypeMethods {
+  public static void registerMethodsWithCovariantReturnType(
+      DexItemFactory factory, Consumer<DexMethod> consumer) {
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ByteBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+            "clear"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ByteBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+            "flip"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ByteBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/ByteBuffer;"), factory.createType("I")),
+            "limit"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ByteBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+            "mark"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ByteBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/ByteBuffer;"), factory.createType("I")),
+            "position"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ByteBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+            "reset"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ByteBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+            "rewind"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/CharBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+            "clear"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/CharBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+            "flip"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/CharBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/CharBuffer;"), factory.createType("I")),
+            "limit"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/CharBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+            "mark"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/CharBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/CharBuffer;"), factory.createType("I")),
+            "position"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/CharBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+            "reset"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/CharBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+            "rewind"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/DoubleBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+            "clear"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/DoubleBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+            "flip"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/DoubleBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/DoubleBuffer;"), factory.createType("I")),
+            "limit"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/DoubleBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+            "mark"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/DoubleBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/DoubleBuffer;"), factory.createType("I")),
+            "position"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/DoubleBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+            "reset"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/DoubleBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+            "rewind"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/FloatBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+            "clear"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/FloatBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+            "flip"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/FloatBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/FloatBuffer;"), factory.createType("I")),
+            "limit"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/FloatBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+            "mark"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/FloatBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/FloatBuffer;"), factory.createType("I")),
+            "position"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/FloatBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+            "reset"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/FloatBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+            "rewind"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/IntBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+            "clear"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/IntBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+            "flip"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/IntBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/IntBuffer;"), factory.createType("I")),
+            "limit"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/IntBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+            "mark"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/IntBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/IntBuffer;"), factory.createType("I")),
+            "position"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/IntBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+            "reset"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/IntBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+            "rewind"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/LongBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+            "clear"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/LongBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+            "flip"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/LongBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/LongBuffer;"), factory.createType("I")),
+            "limit"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/LongBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+            "mark"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/LongBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/LongBuffer;"), factory.createType("I")),
+            "position"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/LongBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+            "reset"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/LongBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+            "rewind"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ShortBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+            "clear"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ShortBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+            "flip"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ShortBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/ShortBuffer;"), factory.createType("I")),
+            "limit"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ShortBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+            "mark"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ShortBuffer;"),
+            factory.createProto(
+                factory.createType("Ljava/nio/ShortBuffer;"), factory.createType("I")),
+            "position"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ShortBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+            "reset"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/nio/ShortBuffer;"),
+            factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+            "rewind"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/time/LocalDate;"),
+            factory.createProto(factory.createType("Ljava/time/chrono/IsoEra;")),
+            "getEra"));
+    consumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/util/concurrent/ConcurrentHashMap;"),
+            factory.createProto(
+                factory.createType("Ljava/util/concurrent/ConcurrentHashMap$KeySetView;")),
+            "keySet"));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 78b9067..deb3f3b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -88,8 +88,6 @@
       DexItemFactory dexItemFactory) {
     // ..., arrayref →
     // ..., length
-    return frame
-        .popInitialized(appView, dexItemFactory.objectArrayType)
-        .push(config, dexItemFactory.intType);
+    return frame.popArray(appView).push(config, dexItemFactory.intType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 8bba186..2d719fd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -24,17 +23,13 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
-import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-public class CfArrayLoad extends CfInstruction {
-
-  private final MemberType type;
+public class CfArrayLoad extends CfArrayLoadOrStore {
 
   public CfArrayLoad(MemberType type) {
-    assert type.isPrecise();
-    this.type = type;
+    super(type);
   }
 
   @Override
@@ -42,18 +37,8 @@
     return getLoadType();
   }
 
-  @Override
-  public int internalAcceptCompareTo(
-      CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
-    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
-  }
-
-  public MemberType getType() {
-    return type;
-  }
-
   private int getLoadType() {
-    switch (type) {
+    switch (getType()) {
       case OBJECT:
         return Opcodes.AALOAD;
       case BOOLEAN_OR_BYTE:
@@ -71,7 +56,7 @@
       case DOUBLE:
         return Opcodes.DALOAD;
       default:
-        throw new Unreachable("Unexpected type " + type);
+        throw new Unreachable("Unexpected type " + getType());
     }
   }
 
@@ -89,34 +74,24 @@
   }
 
   @Override
-  public int bytecodeSizeUpperBound() {
-    return 1;
-  }
-
-  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
 
   @Override
-  public boolean canThrow() {
-    return true;
-  }
-
-  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     Slot index = state.pop();
     Slot array = state.pop();
     Slot value;
     assert array.type.isObject();
-    ValueType memberType = ValueType.fromMemberType(type);
+    ValueType memberType = ValueType.fromMemberType(getType());
     if (array.preciseType != null) {
       value = state.push(array.preciseType.toArrayElementType(builder.appView.dexItemFactory()));
       assert state.peek().type == memberType;
     } else {
       value = state.push(memberType);
     }
-    builder.addArrayGet(type, value.register, array.register, index.register);
+    builder.addArrayGet(getType(), value.register, array.register, index.register);
   }
 
   @Override
@@ -135,7 +110,16 @@
     // ..., value
     return frame
         .popInitialized(appView, dexItemFactory.intType)
-        .popInitialized(appView, dexItemFactory.objectArrayType)
-        .push(appView, config, type);
+        .popInitialized(
+            appView,
+            getExpectedArrayType(dexItemFactory),
+            (state, head) ->
+                head.isNullType()
+                    ? state.push(appView, config, getType())
+                    : state.push(
+                        config,
+                        head.asInitializedReferenceType()
+                            .getInitializedType()
+                            .toArrayElementType(dexItemFactory)));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoadOrStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoadOrStore.java
new file mode 100644
index 0000000..93dd67a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoadOrStore.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2022, 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.cf.code;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+
+public abstract class CfArrayLoadOrStore extends CfInstruction {
+
+  private final MemberType type;
+
+  CfArrayLoadOrStore(MemberType type) {
+    assert type.isPrecise();
+    this.type = type;
+  }
+
+  @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  DexType getExpectedArrayType(DexItemFactory dexItemFactory) {
+    switch (type) {
+      case OBJECT:
+        return dexItemFactory.objectArrayType;
+      case BOOLEAN_OR_BYTE:
+        return dexItemFactory.intArrayType;
+      case CHAR:
+        return dexItemFactory.charArrayType;
+      case SHORT:
+        return dexItemFactory.shortArrayType;
+      case INT:
+        return dexItemFactory.intArrayType;
+      case FLOAT:
+        return dexItemFactory.floatArrayType;
+      case LONG:
+        return dexItemFactory.longArrayType;
+      case DOUBLE:
+        return dexItemFactory.doubleArrayType;
+      default:
+        throw new Unreachable("Unexpected type: " + type);
+    }
+  }
+
+  public MemberType getType() {
+    return type;
+  }
+
+  @Override
+  public int internalAcceptCompareTo(
+      CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 489285f..9435e85 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -23,21 +22,13 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
-import com.android.tools.r8.utils.structural.CompareToVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-public class CfArrayStore extends CfInstruction {
-
-  private final MemberType type;
+public class CfArrayStore extends CfArrayLoadOrStore {
 
   public CfArrayStore(MemberType type) {
-    assert type.isPrecise();
-    this.type = type;
-  }
-
-  public MemberType getType() {
-    return type;
+    super(type);
   }
 
   @Override
@@ -45,14 +36,8 @@
     return getStoreType();
   }
 
-  @Override
-  public int internalAcceptCompareTo(
-      CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
-    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
-  }
-
   private int getStoreType() {
-    switch (type) {
+    switch (getType()) {
       case OBJECT:
         return Opcodes.AASTORE;
       case BOOLEAN_OR_BYTE:
@@ -70,7 +55,7 @@
       case DOUBLE:
         return Opcodes.DASTORE;
       default:
-        throw new Unreachable("Unexpected type " + type);
+        throw new Unreachable("Unexpected type " + getType());
     }
   }
 
@@ -88,26 +73,16 @@
   }
 
   @Override
-  public int bytecodeSizeUpperBound() {
-    return 1;
-  }
-
-  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
 
   @Override
-  public boolean canThrow() {
-    return true;
-  }
-
-  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     Slot value = state.pop();
     Slot index = state.pop();
     Slot array = state.pop();
-    builder.addArrayPut(type, value.register, array.register, index.register);
+    builder.addArrayPut(getType(), value.register, array.register, index.register);
   }
 
   @Override
@@ -125,8 +100,8 @@
     // ..., arrayref, index, value →
     // ...
     return frame
-        .popInitialized(appView, type)
+        .popInitialized(appView, getType())
         .popInitialized(appView, dexItemFactory.intType)
-        .popInitialized(appView, dexItemFactory.objectArrayType);
+        .popInitialized(appView, getExpectedArrayType(dexItemFactory));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
index dc247a4..52854e4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.MapUtils;
 import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
@@ -78,6 +77,7 @@
 
   // Rules found at https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2
   public static boolean isAssignable(DexType source, DexType target, AppView<?> appView) {
+    assert !target.isNullValueType();
     DexItemFactory factory = appView.dexItemFactory();
     source = byteCharShortOrBooleanToInt(source, factory);
     target = byteCharShortOrBooleanToInt(target, factory);
@@ -88,23 +88,30 @@
       return false;
     }
     // Both are now references - everything is assignable to object.
+    assert source.isReferenceType();
+    assert target.isReferenceType();
     if (target == factory.objectType) {
       return true;
     }
     // isAssignable(null, class(_, _)).
     // isAssignable(null, arrayOf(_)).
-    if (source == DexItemFactory.nullValueType) {
+    if (source.isNullValueType()) {
       return true;
     }
-    if (target.isArrayType() != target.isArrayType()) {
-      return false;
-    }
     if (target.isArrayType()) {
-      return isAssignable(
-          target.toArrayElementType(factory), target.toArrayElementType(factory), appView);
+      return source.isArrayType()
+          && isAssignable(
+              source.toArrayElementType(factory), target.toArrayElementType(factory), appView);
     }
+    assert target.isClassType();
+    if (source.isArrayType()) {
+      // Array types are assignable to the class types Object, Cloneable and Serializable.
+      // Object is handled above, so we only need to check the other two.
+      return target == factory.cloneableType || target == factory.serializableType;
+    }
+    assert source.isClassType();
     // TODO(b/166570659): Do a sub-type check that allows for missing classes in hierarchy.
-    return MemberType.fromDexType(source) == MemberType.fromDexType(target);
+    return true;
   }
 
   public static boolean isAssignable(DexType source, ValueType target, AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
index 7c73bbb..4c21ed6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -46,12 +46,13 @@
   public CfFrameVerificationHelper(
       AppView<?> appView,
       CfCode code,
+      GraphLens codeLens,
       ProgramMethod method,
       Map<CfLabel, CfFrame> stateMap,
       List<CfTryCatch> tryCatchRanges) {
     this.appView = appView;
     this.code = code;
-    this.codeLens = code.getCodeLens(appView);
+    this.codeLens = codeLens;
     this.method = method;
     this.previousMethod =
         appView.graphLens().getOriginalMethodSignature(method.getReference(), codeLens);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index ffbd427..b67dda7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -328,10 +328,14 @@
     // ...
     frame = frame.popInitialized(appView, method.getParameters().getBacking());
     if (opcode != Opcodes.INVOKESTATIC) {
-      frame =
-          opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(dexItemFactory)
-              ? frame.popAndInitialize(appView, method, config)
-              : frame.popInitialized(appView, method.getHolderType());
+      if (method.getHolderType().isArrayType()) {
+        frame = frame.popArray(appView);
+      } else {
+        frame =
+            opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(dexItemFactory)
+                ? frame.popAndInitialize(appView, method, config)
+                : frame.popInitialized(appView, method.getHolderType());
+      }
     }
     if (method.getReturnType().isVoidType()) {
       return frame;
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
index ce9f818..392b16d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
@@ -104,6 +104,11 @@
   }
 
   @Override
+  public boolean isInitializedReferenceType() {
+    return true;
+  }
+
+  @Override
   public InitializedReferenceFrameType asInitializedReferenceType() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
index 6522c7e..f614d65 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
@@ -57,8 +57,7 @@
 
   static PrimitiveFrameType primitive(DexType type) {
     assert type.isPrimitiveType();
-    char c = (char) type.getDescriptor().content[0];
-    switch (c) {
+    switch (type.getDescriptor().getFirstByteAsChar()) {
       case 'Z':
         return booleanType();
       case 'B':
@@ -145,6 +144,8 @@
 
   boolean isInitialized();
 
+  boolean isInitializedReferenceType();
+
   InitializedReferenceFrameType asInitializedReferenceType();
 
   boolean isInt();
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
index c2a2152..fd00888 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
@@ -34,6 +34,11 @@
   }
 
   @Override
+  public boolean isInitializedReferenceType() {
+    return true;
+  }
+
+  @Override
   public InitializedReferenceFrameType asInitializedReferenceType() {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index e9b23e7..19bf467 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -29,14 +29,17 @@
 
 public class DebugRepresentation {
 
+  public static final int NO_PC_ENCODING = -1;
+  public static final int ALWAYS_PC_ENCODING = Integer.MAX_VALUE;
+
   public interface DebugRepresentationPredicate {
 
-    boolean useDexPcEncoding(DexProgramClass holder, DexEncodedMethod method);
+    int getDexPcEncodingCutoff(DexProgramClass holder, DexEncodedMethod method);
   }
 
   public static DebugRepresentationPredicate none(InternalOptions options) {
     assert !options.canUseDexPc2PcAsDebugInformation();
-    return (holder, method) -> false;
+    return (holder, method) -> NO_PC_ENCODING;
   }
 
   public static DebugRepresentationPredicate fromFiles(
@@ -44,8 +47,8 @@
     if (!options.canUseDexPc2PcAsDebugInformation()) {
       return none(options);
     }
-    if (options.canUseNativeDexPcInsteadOfDebugInfo() || options.testing.forcePcBasedEncoding) {
-      return (holder, method) -> true;
+    if (options.canUseNativeDexPcInsteadOfDebugInfo()) {
+      return (holder, method) -> ALWAYS_PC_ENCODING;
     }
     // TODO(b/220999985): Avoid the need to maintain a class-to-file map.
     Map<DexProgramClass, VirtualFile> classMapping = new IdentityHashMap<>();
@@ -53,12 +56,15 @@
       file.classes().forEach(c -> classMapping.put(c, file));
     }
     return (holder, method) -> {
-      if (!isPcCandidate(method)) {
-        return false;
+      if (!isPcCandidate(method, options)) {
+        return NO_PC_ENCODING;
       }
       VirtualFile file = classMapping.get(holder);
       DebugRepresentation cutoffs = file.getDebugRepresentation();
-      return cutoffs.usesPcEncoding(method);
+      int maxPc = cutoffs.getDexPcEncodingCutoff(method);
+      assert maxPc == NO_PC_ENCODING
+          || verifyLastExecutableInstructionWithinBound(method.getCode().asDexCode(), maxPc);
+      return maxPc;
     };
   }
 
@@ -71,8 +77,7 @@
   public static void computeForFile(AppView<?> appView, VirtualFile file) {
     InternalOptions options = appView.options();
     if (!options.canUseDexPc2PcAsDebugInformation()
-        || options.canUseNativeDexPcInsteadOfDebugInfo()
-        || options.testing.forcePcBasedEncoding) {
+        || options.canUseNativeDexPcInsteadOfDebugInfo()) {
       return;
     }
     // First collect all of the per-pc costs
@@ -88,11 +93,19 @@
         }
         ProgramMethod method = methods.get(0);
         DexEncodedMethod definition = method.getDefinition();
-        if (!isPcCandidate(definition)) {
+        if (!isPcCandidate(definition, options)) {
           continue;
         }
         DexCode code = definition.getCode().asDexCode();
         DexDebugInfo debugInfo = code.getDebugInfo();
+        if (debugInfo == null) {
+          // If debug info is "null" then the cost of representing it as normal events will be a
+          // single default event to ensure its source file content is active.
+          debugInfo =
+              LineNumberOptimizer.createEventBasedInfoForMethodWithoutDebugInfo(
+                  definition, options.dexItemFactory());
+        }
+        assert debugInfo.getParameterCount() == method.getParameters().size();
         DexInstruction lastInstruction = getLastExecutableInstruction(code);
         if (lastInstruction == null) {
           continue;
@@ -105,26 +118,28 @@
       }
     }
     // Second compute the cost of converting to a pc encoding.
-    paramCountToCosts.forEach((ignored, summary) -> summary.computeConversionCosts());
+    paramCountToCosts.forEach((ignored, summary) -> summary.computeConversionCosts(appView));
     // The result is stored on the virtual files for thread safety.
     // TODO(b/220999985): Consider just passing this to the line number optimizer once fixed.
     file.setDebugRepresentation(new DebugRepresentation(paramCountToCosts));
   }
 
-  private boolean usesPcEncoding(DexEncodedMethod method) {
+  private int getDexPcEncodingCutoff(DexEncodedMethod method) {
     DexCode code = method.getCode().asDexCode();
-    DexDebugInfo debugInfo = code.getDebugInfo();
-    int paramCount = debugInfo.getParameterCount();
+    int paramCount = method.getParameters().size();
+    assert code.getDebugInfo() == null || code.getDebugInfo().getParameterCount() == paramCount;
     CostSummary conversionInfo = paramToInfo.get(paramCount);
-    if (conversionInfo.cutoff < 0) {
-      return false;
+    if (conversionInfo == null || conversionInfo.cutoff < 0) {
+      // We expect all methods calling this to have computed conversion info.
+      assert conversionInfo != null;
+      return NO_PC_ENCODING;
     }
     DexInstruction lastInstruction = getLastExecutableInstruction(code);
     if (lastInstruction == null) {
-      return false;
+      return NO_PC_ENCODING;
     }
     int maxPc = lastInstruction.getOffset();
-    return maxPc <= conversionInfo.cutoff;
+    return maxPc <= conversionInfo.cutoff ? conversionInfo.cutoff : NO_PC_ENCODING;
   }
 
   @Override
@@ -134,12 +149,12 @@
     return StringUtils.join("\n", sorted, CostSummary::toString);
   }
 
-  private static boolean isPcCandidate(DexEncodedMethod method) {
+  private static boolean isPcCandidate(DexEncodedMethod method, InternalOptions options) {
     if (!method.hasCode() || !method.getCode().isDexCode()) {
       return false;
     }
     DexCode code = method.getCode().asDexCode();
-    return LineNumberOptimizer.doesContainPositions(code);
+    return LineNumberOptimizer.mustHaveResidualDebugInfo(code, options);
   }
 
   /** The cost of representing normal debug info for all methods with this max pc value. */
@@ -188,7 +203,8 @@
       maxPc = Math.max(maxPc, pc);
     }
 
-    private void computeConversionCosts() {
+    private void computeConversionCosts(AppView<?> appView) {
+      boolean forcePcBasedEncoding = appView.options().testing.forcePcBasedEncoding;
       assert !pcToCost.isEmpty();
       // Point at which it is estimated that conversion to PC-encoding is viable.
       int currentConvertedPc = -1;
@@ -211,7 +227,7 @@
         // If the estimated cost is larger we convert. The order here could be either way as
         // both the normal cost and converted cost are estimates. Canonicalization could reduce
         // the former and compaction could reduce the latter.
-        if (normalOutstandingCost > costToConvert) {
+        if (forcePcBasedEncoding || normalOutstandingCost > costToConvert) {
           normalConvertedCost += normalOutstandingCost;
           normalOutstandingCost = 0;
           currentConvertedPc = currentPc;
@@ -263,7 +279,14 @@
     }
   }
 
-  private static DexInstruction getLastExecutableInstruction(DexCode code) {
+  public static boolean verifyLastExecutableInstructionWithinBound(DexCode code, int maxPc) {
+    DexInstruction lastExecutableInstruction = getLastExecutableInstruction(code);
+    int offset = lastExecutableInstruction.getOffset();
+    assert offset <= maxPc;
+    return true;
+  }
+
+  public static DexInstruction getLastExecutableInstruction(DexCode code) {
     DexInstruction lastInstruction = null;
     for (DexInstruction instruction : code.instructions) {
       if (!instruction.isPayload()) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 95358b2..d4f1195 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -739,6 +739,12 @@
               clazz.getNestHostClassAttribute(), options.itemFactory));
     }
 
+    if (clazz.hasPermittedSubclassAttributes() && options.canUseSealedClasses()) {
+      annotations.add(
+          DexAnnotation.createPermittedSubclassesAnnotation(
+              clazz.getPermittedSubclassAttributes(), options.itemFactory));
+    }
+
     if (!annotations.isEmpty()) {
       // Append the annotations to annotations array of the class.
       DexAnnotation[] copy =
@@ -753,6 +759,7 @@
     clazz.clearEnclosingMethodAttribute();
     clazz.clearInnerClasses();
     clazz.clearClassSignature();
+    clazz.clearPermittedSubclasses();
   }
 
   private void insertAttributeAnnotationsForField(DexEncodedField field) {
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 10132f3..50a91d9 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -65,10 +65,12 @@
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.io.ByteStreams;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
@@ -859,6 +861,7 @@
               source,
               attrs.nestHostAttribute,
               attrs.nestMembersAttribute,
+              attrs.permittedSubclassesAttribute,
               attrs.getEnclosingMethodAttribute(),
               attrs.getInnerClasses(),
               attrs.classSignature,
@@ -1427,6 +1430,7 @@
     private ClassSignature classSignature = ClassSignature.noSignature();
     private NestHostClassAttribute nestHostAttribute;
     private List<NestMemberClassAttribute> nestMembersAttribute = Collections.emptyList();
+    private List<PermittedSubclassAttribute> permittedSubclassesAttribute = Collections.emptyList();
 
     public DexAnnotationSet getAnnotations() {
       if (lazyAnnotations != null) {
@@ -1504,6 +1508,14 @@
               nestMembersAttribute.add(new NestMemberClassAttribute(member));
             }
           }
+        } else if (DexAnnotation.isPermittedSubclassesAnnotation(annotation, factory)) {
+          ensureAnnotations(i);
+          List<DexType> permittedSubclasses =
+              DexAnnotation.getPermittedSubclassesFromAnnotation(annotation, factory);
+          if (permittedSubclasses != null) {
+            permittedSubclassesAttribute =
+                ListUtils.map(permittedSubclasses, PermittedSubclassAttribute::new);
+          }
         } else {
           copyAnnotation(annotation);
         }
diff --git a/src/main/java/com/android/tools/r8/errors/AssumeValuesMissingStaticFieldDiagnostic.java b/src/main/java/com/android/tools/r8/errors/AssumeValuesMissingStaticFieldDiagnostic.java
new file mode 100644
index 0000000..38f6a46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/AssumeValuesMissingStaticFieldDiagnostic.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2022, 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class AssumeValuesMissingStaticFieldDiagnostic implements Diagnostic {
+
+  private final DexType fieldHolder;
+  private final DexString fieldName;
+  private final Origin origin;
+  private final Position position;
+
+  private AssumeValuesMissingStaticFieldDiagnostic(
+      DexType fieldHolder, DexString fieldName, Origin origin, Position position) {
+    this.fieldHolder = fieldHolder;
+    this.fieldName = fieldName;
+    this.origin = origin;
+    this.position = position;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "The field "
+        + fieldHolder.getTypeName()
+        + "."
+        + fieldName
+        + " is used as the return value in an -assumenosideeffects or -assumevalues rule"
+        + ", but no such static field exists.";
+  }
+
+  public static class Builder {
+
+    private DexType fieldHolder;
+    private DexString fieldName;
+    private Origin origin;
+    private Position position;
+
+    public Builder() {}
+
+    public Builder setField(DexType fieldHolder, DexString fieldName) {
+      this.fieldHolder = fieldHolder;
+      this.fieldName = fieldName;
+      return this;
+    }
+
+    public Builder setOrigin(Origin origin) {
+      this.origin = origin;
+      return this;
+    }
+
+    public Builder setPosition(Position position) {
+      this.position = position;
+      return this;
+    }
+
+    public AssumeValuesMissingStaticFieldDiagnostic build() {
+      return new AssumeValuesMissingStaticFieldDiagnostic(fieldHolder, fieldName, origin, position);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/CheckEnumUnboxedDiagnostic.java b/src/main/java/com/android/tools/r8/errors/CheckEnumUnboxedDiagnostic.java
new file mode 100644
index 0000000..3339192
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/CheckEnumUnboxedDiagnostic.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.google.common.collect.ImmutableList;
+import java.util.Comparator;
+import java.util.List;
+
+@Keep
+public class CheckEnumUnboxedDiagnostic implements Diagnostic {
+
+  private final List<String> messages;
+
+  CheckEnumUnboxedDiagnostic(List<String> messages) {
+    this.messages = messages;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** The origin of a -checkenumunboxed failure is not unique. (The whole app is to blame.) */
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  /** The position of a -checkenumunboxed failure is always unknown. */
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    StringBuilder builder = new StringBuilder("Enum unboxing checks failed.");
+    for (String message : messages) {
+      builder.append(System.lineSeparator());
+      builder.append(message);
+    }
+    return builder.toString();
+  }
+
+  public static class Builder {
+
+    private final ImmutableList.Builder<String> messagesBuilder = ImmutableList.builder();
+
+    public Builder addFailedEnums(List<DexProgramClass> failed) {
+      failed.sort(Comparator.comparing(DexClass::getType));
+      for (DexProgramClass clazz : failed) {
+        messagesBuilder.add("Enum " + clazz.getTypeName() + " was not unboxed.");
+      }
+      return this;
+    }
+
+    public CheckEnumUnboxedDiagnostic build() {
+      return new CheckEnumUnboxedDiagnostic(messagesBuilder.build());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
index 7fad730..c633722 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
@@ -4,20 +4,21 @@
 
 package com.android.tools.r8.experimental.startup;
 
-import static com.android.tools.r8.utils.InternalOptions.getSystemPropertyForDevelopmentOrDefault;
-import static com.android.tools.r8.utils.InternalOptions.isSystemPropertyForDevelopmentSet;
+import static com.android.tools.r8.utils.SystemPropertyUtils.getSystemPropertyForDevelopment;
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
 
 public class StartupOptions {
 
   private boolean enableMinimalStartupDex =
-      isSystemPropertyForDevelopmentSet("com.android.tools.r8.startup.minimalstartupdex");
+      parseSystemPropertyForDevelopmentOrDefault(
+          "com.android.tools.r8.startup.minimalstartupdex", false);
   private boolean enableStartupCompletenessCheckForTesting =
-      isSystemPropertyForDevelopmentSet("com.android.tools.r8.startup.completenesscheck");
+      parseSystemPropertyForDevelopmentOrDefault(
+          "com.android.tools.r8.startup.completenesscheck", false);
   private boolean enableStartupInstrumentation =
-      isSystemPropertyForDevelopmentSet("com.android.tools.r8.startup.instrument");
+      parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.startup.instrument", false);
   private String startupInstrumentationTag =
-      getSystemPropertyForDevelopmentOrDefault(
-          "com.android.tools.r8.startup.instrumentationtag", null);
+      getSystemPropertyForDevelopment("com.android.tools.r8.startup.instrumentationtag");
 
   private StartupConfiguration startupConfiguration;
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 6dd2ee7..b4926fe 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -34,7 +34,9 @@
 import com.android.tools.r8.naming.SeedMapper;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.optimize.interfaces.collection.OpenClosedInterfacesCollection;
+import com.android.tools.r8.retrace.internal.RetraceUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
 import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -74,6 +76,7 @@
   private T appInfo;
   private AppInfoWithClassHierarchy appInfoForDesugaring;
   private AppServices appServices;
+  private AssumeInfoCollection assumeInfoCollection = AssumeInfoCollection.builder().build();
   private final DontWarnConfiguration dontWarnConfiguration;
   private final WholeProgramOptimizations wholeProgramOptimizations;
   private GraphLens codeLens = GraphLens.getIdentityLens();
@@ -115,7 +118,8 @@
       OpenClosedInterfacesCollection.getDefault();
   // TODO(b/169115389): Remove
   private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
-  private Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
+  private final Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
+  private final Map<DexType, String> sourceFileForPrunedTypes = new IdentityHashMap<>();
 
   private SeedMapper applyMappingSeedMapper;
 
@@ -311,6 +315,14 @@
     this.appServices = appServices;
   }
 
+  public AssumeInfoCollection getAssumeInfoCollection() {
+    return assumeInfoCollection;
+  }
+
+  public void setAssumeInfoCollection(AssumeInfoCollection assumeInfoCollection) {
+    this.assumeInfoCollection = assumeInfoCollection;
+  }
+
   public DontWarnConfiguration getDontWarnConfiguration() {
     return dontWarnConfiguration;
   }
@@ -760,6 +772,7 @@
     if (appServices() != null) {
       setAppServices(appServices().prunedCopy(prunedItems));
     }
+    setAssumeInfoCollection(getAssumeInfoCollection().withoutPrunedItems(prunedItems));
     if (hasProguardCompatibilityActions()) {
       setProguardCompatibilityActions(
           getProguardCompatibilityActions().withoutPrunedItems(prunedItems));
@@ -857,6 +870,8 @@
                 .setAppInfo(appView.appInfoWithLiveness().rewrittenWithLens(application, lens));
           }
           appView.setAppServices(appView.appServices().rewrittenWithLens(lens));
+          appView.setAssumeInfoCollection(
+              appView.getAssumeInfoCollection().rewrittenWithLens(appView, lens));
           if (appView.hasInitClassLens()) {
             appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
           }
@@ -915,4 +930,14 @@
   public ComputedApiLevel computedMinApiLevel() {
     return computedMinApiLevel;
   }
+
+  public void addPrunedClassSourceFile(DexType prunedType, String sourceFile) {
+    if (!RetraceUtils.hasPredictableSourceFileName(prunedType.toSourceString(), sourceFile)) {
+      sourceFileForPrunedTypes.put(prunedType, sourceFile);
+    }
+  }
+
+  public String getPrunedClassSourceFileInfo(DexType dexType) {
+    return sourceFileForPrunedTypes.get(dexType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 42d0bcd..f0598cd 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -953,7 +953,7 @@
           CfCodeStackMapValidatingException.noFramesForMethodWithJumps(method, appView), appView);
     }
     CfFrameVerificationHelper helper =
-        new CfFrameVerificationHelper(appView, this, method, stateMap, tryCatchRanges);
+        new CfFrameVerificationHelper(appView, this, codeLens, method, stateMap, tryCatchRanges);
     CfCodeDiagnostics diagnostics = helper.checkTryCatchRanges();
     if (diagnostics != null) {
       return reportStackMapError(diagnostics, appView);
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 60f78ea..e878e0d 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -26,6 +26,7 @@
               sourceFile,
               nestHost,
               nestMembers,
+              permittedSubclasses,
               enclosingMember,
               innerClasses,
               classSignature,
@@ -47,6 +48,7 @@
                   sourceFile,
                   nestHost,
                   nestMembers,
+                  permittedSubclasses,
                   enclosingMember,
                   innerClasses,
                   classSignature,
@@ -69,6 +71,7 @@
               sourceFile,
               nestHost,
               nestMembers,
+              permittedSubclasses,
               enclosingMember,
               innerClasses,
               classSignature,
@@ -90,6 +93,7 @@
                   sourceFile,
                   nestHost,
                   nestMembers,
+                  permittedSubclasses,
                   enclosingMember,
                   innerClasses,
                   classSignature,
@@ -110,6 +114,7 @@
               sourceFile,
               nestHost,
               nestMembers,
+              permittedSubclasses,
               enclosingMember,
               innerClasses,
               classSignature,
@@ -131,6 +136,7 @@
                   sourceFile,
                   nestHost,
                   nestMembers,
+                  permittedSubclasses,
                   enclosingMember,
                   innerClasses,
                   classSignature,
@@ -152,6 +158,7 @@
         DexString sourceFile,
         NestHostClassAttribute nestHost,
         List<NestMemberClassAttribute> nestMembers,
+        List<PermittedSubclassAttribute> permittedSubclasses,
         EnclosingMethodAttribute enclosingMember,
         List<InnerClassAttribute> innerClasses,
         ClassSignature classSignature,
@@ -183,6 +190,7 @@
       DexString sourceFile,
       NestHostClassAttribute nestHost,
       List<NestMemberClassAttribute> nestMembers,
+      List<PermittedSubclassAttribute> permittedSubclasses,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
       ClassSignature classSignature,
@@ -204,6 +212,7 @@
         sourceFile,
         nestHost,
         nestMembers,
+        permittedSubclasses,
         enclosingMember,
         innerClasses,
         classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index c43d396..924590f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -186,6 +186,11 @@
     return annotation.annotation.type == factory.annotationNestMembers;
   }
 
+  public static boolean isPermittedSubclassesAnnotation(
+      DexAnnotation annotation, DexItemFactory factory) {
+    return annotation.annotation.type == factory.annotationPermittedSubclasses;
+  }
+
   public static DexAnnotation createInnerClassAnnotation(
       DexString clazz, int access, DexItemFactory factory) {
     return new DexAnnotation(
@@ -252,9 +257,9 @@
     return value.asDexValueType().getValue();
   }
 
-  public static List<DexType> getNestMembersFromAnnotation(
-      DexAnnotation annotation, DexItemFactory factory) {
-    DexValue value = getSystemValueAnnotationValue(factory.annotationNestMembers, annotation);
+  private static List<DexType> getTypesFromAnnotation(
+      DexType annotationType, DexAnnotation annotation) {
+    DexValue value = getSystemValueAnnotationValue(annotationType, annotation);
     if (value == null) {
       return null;
     }
@@ -266,6 +271,16 @@
     return types;
   }
 
+  public static List<DexType> getNestMembersFromAnnotation(
+      DexAnnotation annotation, DexItemFactory factory) {
+    return getTypesFromAnnotation(factory.annotationNestMembers, annotation);
+  }
+
+  public static List<DexType> getPermittedSubclassesFromAnnotation(
+      DexAnnotation annotation, DexItemFactory factory) {
+    return getTypesFromAnnotation(factory.annotationPermittedSubclasses, annotation);
+  }
+
   public static DexAnnotation createSourceDebugExtensionAnnotation(DexValue value,
       DexItemFactory factory) {
     return new DexAnnotation(VISIBILITY_SYSTEM,
@@ -322,6 +337,18 @@
         new DexValue.DexValueArray(list.toArray(DexValue.EMPTY_ARRAY)));
   }
 
+  public static DexAnnotation createPermittedSubclassesAnnotation(
+      List<PermittedSubclassAttribute> permittedSubclasses, DexItemFactory factory) {
+    List<DexValueType> list = new ArrayList<>(permittedSubclasses.size());
+    for (PermittedSubclassAttribute permittedSubclass : permittedSubclasses) {
+      list.add(new DexValue.DexValueType(permittedSubclass.getPermittedSubclass()));
+    }
+    return createSystemValueAnnotation(
+        factory.annotationPermittedSubclasses,
+        factory,
+        new DexValue.DexValueArray(list.toArray(DexValue.EMPTY_ARRAY)));
+  }
+
   public static String getSignature(DexAnnotation signatureAnnotation) {
     DexValueArray elements = signatureAnnotation.annotation.elements[0].value.asDexValueArray();
     StringBuilder signature = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index a377b46..f10a654 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -80,6 +80,8 @@
 
   private List<NestMemberClassAttribute> nestMembers;
 
+  private List<PermittedSubclassAttribute> permittedSubclasses;
+
   /** Generic signature information if the attribute is present in the input */
   protected ClassSignature classSignature;
 
@@ -94,6 +96,7 @@
       MethodCollectionFactory methodCollectionFactory,
       NestHostClassAttribute nestHost,
       List<NestMemberClassAttribute> nestMembers,
+      List<PermittedSubclassAttribute> permittedSubclasses,
       EnclosingMethodAttribute enclosingMethod,
       List<InnerClassAttribute> innerClasses,
       ClassSignature classSignature,
@@ -114,6 +117,8 @@
     this.nestHost = nestHost;
     this.nestMembers = nestMembers;
     assert nestMembers != null;
+    this.permittedSubclasses = permittedSubclasses;
+    assert permittedSubclasses != null;
     this.enclosingMethod = enclosingMethod;
     this.innerClasses = innerClasses;
     assert classSignature != null;
@@ -539,17 +544,26 @@
     return lookupTarget(instanceFields, field);
   }
 
-  public DexField lookupUniqueInstanceFieldWithName(DexString name) {
-    DexField field = null;
-    for (DexEncodedField encodedField : instanceFields()) {
-      if (encodedField.getReference().name == name) {
-        if (field != null) {
+  public DexEncodedField lookupUniqueInstanceFieldWithName(DexString name) {
+    return internalLookupUniqueFieldThatMatches(field -> field.getName() == name, instanceFields());
+  }
+
+  public DexEncodedField lookupUniqueStaticFieldWithName(DexString name) {
+    return internalLookupUniqueFieldThatMatches(field -> field.getName() == name, staticFields());
+  }
+
+  private static DexEncodedField internalLookupUniqueFieldThatMatches(
+      Predicate<DexEncodedField> predicate, List<DexEncodedField> fields) {
+    DexEncodedField result = null;
+    for (DexEncodedField field : fields) {
+      if (predicate.test(field)) {
+        if (result != null) {
           return null;
         }
-        field = encodedField.getReference();
+        result = field;
       }
     }
-    return field;
+    return result;
   }
 
   /** Find method in this class matching {@param method}. */
@@ -1102,6 +1116,10 @@
     this.classSignature = classSignature;
   }
 
+  public void clearPermittedSubclasses() {
+    permittedSubclasses.clear();
+  }
+
   public boolean isLocalClass() {
     InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
     // The corresponding enclosing-method attribute might be not available, e.g., CF version 50.
@@ -1171,7 +1189,7 @@
   }
 
   public boolean hasNestMemberAttributes() {
-    return nestMembers != null && !nestMembers.isEmpty();
+    return !nestMembers.isEmpty();
   }
 
   public List<NestMemberClassAttribute> getNestMembersClassAttributes() {
@@ -1186,6 +1204,14 @@
     nestMembers.removeIf(predicate);
   }
 
+  public boolean hasPermittedSubclassAttributes() {
+    return !permittedSubclasses.isEmpty();
+  }
+
+  public List<PermittedSubclassAttribute> getPermittedSubclassAttributes() {
+    return permittedSubclasses;
+  }
+
   /** Returns kotlin class info if the class is synthesized by kotlin compiler. */
   public abstract KotlinClassLevelInfo getKotlinInfo();
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 5130c57..43c2532 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -35,6 +35,7 @@
       DexString sourceFile,
       NestHostClassAttribute nestHost,
       List<NestMemberClassAttribute> nestMembers,
+      List<PermittedSubclassAttribute> permittedSubclasses,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
       ClassSignature classSignature,
@@ -54,6 +55,7 @@
         methodCollectionFactory,
         nestHost,
         nestMembers,
+        permittedSubclasses,
         enclosingMember,
         innerClasses,
         classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index b6f5fd9..8469b4e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.debuginfo.DebugRepresentation;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.DebugBytecodeWriter;
 import com.android.tools.r8.dex.IndexedItemCollection;
@@ -97,6 +98,10 @@
       this.maxPc = maxPc;
     }
 
+    public int getMaxPc() {
+      return maxPc;
+    }
+
     @Override
     public int getStartLine() {
       return START_LINE;
@@ -294,19 +299,18 @@
     }
     assert code.getDebugInfo().isPcBasedInfo();
     PcBasedDebugInfo pcBasedDebugInfo = code.getDebugInfo().asPcBasedInfo();
+    assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
+        code, pcBasedDebugInfo.maxPc);
     // Generate a line event at each throwing instruction.
     List<DexDebugEvent> events = new ArrayList<>(code.instructions.length);
-    int pc = 0;
     int delta = 0;
     for (DexInstruction instruction : code.instructions) {
       if (instruction.canThrow()) {
         DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary(delta, delta, events, factory);
-        pc += delta;
         delta = 0;
       }
       delta += instruction.getSize();
     }
-    assert pc + delta - ArrayUtils.last(code.instructions).getSize() <= pcBasedDebugInfo.maxPc;
     return new EventBasedDebugInfo(
         PcBasedDebugInfo.START_LINE,
         new DexString[pcBasedDebugInfo.getParameterCount()],
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 36f8d0f..e9f0446 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -646,6 +646,8 @@
       createStaticallyKnownType("Ldalvik/annotation/NestHost;");
   public final DexType annotationNestMembers =
       createStaticallyKnownType("Ldalvik/annotation/NestMembers;");
+  public final DexType annotationPermittedSubclasses =
+      createStaticallyKnownType("Ldalvik/annotation/PermittedSubclasses;");
   public final DexType annotationSourceDebugExtension =
       createStaticallyKnownType("Ldalvik/annotation/SourceDebugExtension;");
   public final DexType annotationThrows = createStaticallyKnownType("Ldalvik/annotation/Throws;");
@@ -750,9 +752,7 @@
 
   public BoxedPrimitiveMembers getBoxedMembersForPrimitiveOrVoidType(DexType type) {
     assert type.isPrimitiveType() || type.isVoidType();
-    char c = (char) type.getDescriptor().content[0];
-    assert c == type.toDescriptorString().charAt(0);
-    switch (c) {
+    switch (type.getDescriptor().getFirstByteAsChar()) {
       case 'B':
         return byteMembers;
       case 'C':
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index e5cd61c..24091e4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -33,6 +33,7 @@
       DexString sourceFile,
       NestHostClassAttribute nestHost,
       List<NestMemberClassAttribute> nestMembers,
+      List<PermittedSubclassAttribute> permittedSubclasses,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
       ClassSignature classSignature,
@@ -52,6 +53,7 @@
         methodCollectionFactory,
         nestHost,
         nestMembers,
+        permittedSubclasses,
         enclosingMember,
         innerClasses,
         classSignature,
@@ -171,6 +173,7 @@
     private DexString sourceFile = null;
     private NestHostClassAttribute nestHost = null;
     private List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
+    private List<PermittedSubclassAttribute> permittedSubclasses = Collections.emptyList();
     private EnclosingMethodAttribute enclosingMember = null;
     private List<InnerClassAttribute> innerClasses = Collections.emptyList();
     private ClassSignature classSignature = ClassSignature.noSignature();
@@ -213,6 +216,7 @@
           sourceFile,
           nestHost,
           nestMembers,
+          permittedSubclasses,
           enclosingMember,
           innerClasses,
           classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 46bebdc..b49abbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -69,6 +69,7 @@
       DexString sourceFile,
       NestHostClassAttribute nestHost,
       List<NestMemberClassAttribute> nestMembers,
+      List<PermittedSubclassAttribute> permittedSubclasses,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
       ClassSignature classSignature,
@@ -90,6 +91,7 @@
         methodCollectionFactory,
         nestHost,
         nestMembers,
+        permittedSubclasses,
         enclosingMember,
         innerClasses,
         classSignature,
@@ -113,6 +115,7 @@
       DexString sourceFile,
       NestHostClassAttribute nestHost,
       List<NestMemberClassAttribute> nestMembers,
+      List<PermittedSubclassAttribute> permittedSubclasses,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
       ClassSignature classSignature,
@@ -132,6 +135,7 @@
         sourceFile,
         nestHost,
         nestMembers,
+        permittedSubclasses,
         enclosingMember,
         innerClasses,
         classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 87630f2..f6d0d19 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -35,6 +35,10 @@
     this.content = encodeToMutf8(string);
   }
 
+  public char getFirstByteAsChar() {
+    return (char) content[0];
+  }
+
   @Override
   public DexString self() {
     return this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index df11e7e..1efc762 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -223,7 +223,7 @@
   }
 
   public char toShorty() {
-    char c = (char) descriptor.content[0];
+    char c = descriptor.getFirstByteAsChar();
     return c == '[' ? 'L' : c;
   }
 
@@ -266,58 +266,58 @@
   }
 
   public boolean isPrimitiveType() {
-    return DescriptorUtils.isPrimitiveType((char) descriptor.content[0]);
+    return DescriptorUtils.isPrimitiveType(descriptor.getFirstByteAsChar());
   }
 
   public boolean isVoidType() {
-    return (char) descriptor.content[0] == 'V';
+    return descriptor.getFirstByteAsChar() == 'V';
   }
 
   public boolean isBooleanType() {
-    return descriptor.content[0] == 'Z';
+    return descriptor.getFirstByteAsChar() == 'Z';
   }
 
   public boolean isByteType() {
-    return descriptor.content[0] == 'B';
+    return descriptor.getFirstByteAsChar() == 'B';
   }
 
   public boolean isCharType() {
-    return descriptor.content[0] == 'C';
+    return descriptor.getFirstByteAsChar() == 'C';
   }
 
   public boolean isShortType() {
-    return descriptor.content[0] == 'S';
+    return descriptor.getFirstByteAsChar() == 'S';
   }
 
   public boolean isIntType() {
-    return descriptor.content[0] == 'I';
+    return descriptor.getFirstByteAsChar() == 'I';
   }
 
   public boolean isFloatType() {
-    return descriptor.content[0] == 'F';
+    return descriptor.getFirstByteAsChar() == 'F';
   }
 
   public boolean isLongType() {
-    return descriptor.content[0] == 'J';
+    return descriptor.getFirstByteAsChar() == 'J';
   }
 
   public boolean isDoubleType() {
-    return descriptor.content[0] == 'D';
+    return descriptor.getFirstByteAsChar() == 'D';
   }
 
   public boolean isNullValueType() {
-    boolean isNullValueType = descriptor.content[0] == 'N';
+    boolean isNullValueType = descriptor.getFirstByteAsChar() == 'N';
     assert !isNullValueType || this == DexItemFactory.nullValueType;
     return isNullValueType;
   }
 
   public boolean isArrayType() {
-    char firstChar = (char) descriptor.content[0];
+    char firstChar = descriptor.getFirstByteAsChar();
     return firstChar == '[';
   }
 
   public boolean isClassType() {
-    char firstChar = (char) descriptor.content[0];
+    char firstChar = descriptor.getFirstByteAsChar();
     return firstChar == 'L';
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index d88983a..0237c6d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -226,6 +226,7 @@
     private DexString sourceFile;
     private NestHostClassAttribute nestHost = null;
     private final List<NestMemberClassAttribute> nestMembers = new ArrayList<>();
+    private final List<PermittedSubclassAttribute> permittedSubclasses = new ArrayList<>();
     private final Set<DexField> recordComponents = Sets.newIdentityHashSet();
     private EnclosingMethodAttribute enclosingMember = null;
     private final List<InnerClassAttribute> innerClasses = new ArrayList<>();
@@ -348,11 +349,9 @@
 
     @Override
     public void visitPermittedSubclass(String permittedSubclass) {
-      if (classKind == ClassKind.PROGRAM) {
-        throw new CompilationError("Sealed classes are not supported as program classes", origin);
-      }
-      // For library and classpath just ignore the permitted subclasses, as the compiler is not
-      // validating the code with respect to sealed classes.
+      assert permittedSubclass != null;
+      DexType permittedSubclassType = application.getTypeFromName(permittedSubclass);
+      permittedSubclasses.add(new PermittedSubclassAttribute(permittedSubclassType));
     }
 
     @Override
@@ -476,6 +475,7 @@
               sourceFile,
               nestHost,
               nestMembers,
+              permittedSubclasses,
               enclosingMember,
               innerClasses,
               classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/PermittedSubclassAttribute.java b/src/main/java/com/android/tools/r8/graph/PermittedSubclassAttribute.java
new file mode 100644
index 0000000..0c4dd43
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/PermittedSubclassAttribute.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2022, 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.graph;
+
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.util.Collections;
+import java.util.List;
+import org.objectweb.asm.ClassWriter;
+
+public class PermittedSubclassAttribute implements StructuralItem<PermittedSubclassAttribute> {
+
+  private final DexType permittedSubclass;
+
+  private static void specify(StructuralSpecification<PermittedSubclassAttribute, ?> spec) {
+    spec.withItem(a -> a.permittedSubclass);
+  }
+
+  public PermittedSubclassAttribute(DexType nestMember) {
+    this.permittedSubclass = nestMember;
+  }
+
+  public static List<PermittedSubclassAttribute> emptyList() {
+    return Collections.emptyList();
+  }
+
+  public DexType getPermittedSubclass() {
+    return permittedSubclass;
+  }
+
+  public void write(ClassWriter writer, NamingLens lens) {
+    assert permittedSubclass != null;
+    writer.visitPermittedSubclass(lens.lookupInternalName(permittedSubclass));
+  }
+
+  @Override
+  public PermittedSubclassAttribute self() {
+    return this;
+  }
+
+  @Override
+  public StructuralMapping<PermittedSubclassAttribute> getStructuralMapping() {
+    return PermittedSubclassAttribute::specify;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 0828ff2..1c99b6c 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -107,6 +107,7 @@
             clazz.getSourceFile(),
             fixupNestHost(clazz.getNestHostClassAttribute()),
             fixupNestMemberAttributes(clazz.getNestMembersClassAttributes()),
+            fixupPermittedSubclassAttribute(clazz.getPermittedSubclassAttributes()),
             fixupEnclosingMethodAttribute(clazz.getEnclosingMethodAttribute()),
             fixupInnerClassAttributes(clazz.getInnerClasses()),
             clazz.getClassSignature(),
@@ -271,6 +272,23 @@
     return changed ? newNestMemberAttributes : nestMemberAttributes;
   }
 
+  protected List<PermittedSubclassAttribute> fixupPermittedSubclassAttribute(
+      List<PermittedSubclassAttribute> permittedSubclassAttributes) {
+    if (permittedSubclassAttributes.isEmpty()) {
+      return permittedSubclassAttributes;
+    }
+    boolean changed = false;
+    List<PermittedSubclassAttribute> newPermittedSubclassAttributes =
+        new ArrayList<>(permittedSubclassAttributes.size());
+    for (PermittedSubclassAttribute permittedSubclassAttribute : permittedSubclassAttributes) {
+      DexType permittedSubclassType = permittedSubclassAttribute.getPermittedSubclass();
+      DexType newPermittedSubclassType = fixupType(permittedSubclassType);
+      newPermittedSubclassAttributes.add(new PermittedSubclassAttribute(newPermittedSubclassType));
+      changed |= newPermittedSubclassType != permittedSubclassType;
+    }
+    return changed ? newPermittedSubclassAttributes : permittedSubclassAttributes;
+  }
+
   /** Fixup a proto descriptor. */
   public DexProto fixupProto(DexProto proto) {
     DexProto result = protoFixupCache.get(proto);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index ab305a3..a6e27f5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -60,7 +60,7 @@
         AppView<AppInfoWithLiveness> appView, DexEncodedMethod method) {
       return new MethodCharacteristics(
           method,
-          appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()),
+          appView.getAssumeInfoCollection().isSideEffectFree(method.getReference()),
           appView.appInfo().getMainDexInfo().isTracedMethodRoot(method.getReference()));
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index eb22b19..fbe3ff5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -202,10 +202,10 @@
         return new ProtoTypeObject(constClass.getValue());
       } else if (definition.isConstString()) {
         ConstString constString = definition.asConstString();
-        DexField field =
+        DexEncodedField field =
             context.getHolder().lookupUniqueInstanceFieldWithName(constString.getValue());
         if (field != null) {
-          return new LiveProtoFieldObject(field);
+          return new LiveProtoFieldObject(field.getReference());
         }
         // This const-string refers to a field that no longer exists. In this case, we create a
         // special dead-object instead of failing with an InvalidRawMessageInfoException below.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index bc1a554..bab5e75 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -31,6 +31,11 @@
     return knownArrayLengthStates.computeIfAbsent(length, KnownLengthArrayState::new);
   }
 
+  public NumberFromIntervalValue createNumberFromIntervalValue(
+      long minInclusive, long maxInclusive) {
+    return new NumberFromIntervalValue(minInclusive, maxInclusive);
+  }
+
   public SingleFieldValue createSingleFieldValue(DexField field, ObjectState state) {
     return state.isEmpty()
         ? new SingleStatelessFieldValue(field)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
index 3238680..94d2d59 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
@@ -31,6 +31,14 @@
     return maxInclusive - minInclusive + 1;
   }
 
+  public long getMinInclusive() {
+    return minInclusive;
+  }
+
+  public long getMaxInclusive() {
+    return maxInclusive;
+  }
+
   @Override
   public boolean isNumberFromIntervalValue() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index d090011..fe41e44 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -67,7 +67,7 @@
 
   @Override
   public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       ProgramMethod context,
       NumberGenerator valueNumberGenerator,
       TypeAndLocalInfoSupplier info) {
@@ -88,8 +88,8 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+  boolean internalIsMaterializableInContext(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
       DexClass clazz = appView.definitionFor(baseType);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
index 8a69170..c1e6c43 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
@@ -75,7 +75,7 @@
 
   @Override
   public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       ProgramMethod context,
       NumberGenerator valueNumberGenerator,
       TypeAndLocalInfoSupplier info) {
@@ -97,8 +97,8 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+  boolean internalIsMaterializableInContext(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 1c50dd7..d7442d5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -85,19 +85,21 @@
 
   @Override
   public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       ProgramMethod context,
       NumberGenerator valueNumberGenerator,
       TypeAndLocalInfoSupplier info) {
     TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
-    assert type.lessThanOrEqual(info.getOutType(), appView) || type.isBasedOnMissingClass(appView);
+    assert type.lessThanOrEqual(info.getOutType(), appView)
+        || !appView.enableWholeProgramOptimizations()
+        || type.isBasedOnMissingClass(appView.withClassHierarchy());
     Value outValue = new Value(valueNumberGenerator.next(), type, info.getLocalInfo());
     return new StaticGet(outValue, field);
   }
 
   @Override
-  public boolean isMaterializableInContext(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+  boolean internalIsMaterializableInContext(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     return allMatch(
         appView.appInfo().resolveField(field)::forEachFieldResolutionResult,
         resolutionResult -> {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index c9b7f62..934f0e6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -127,7 +127,7 @@
 
   @Override
   public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       ProgramMethod context,
       NumberGenerator valueNumberGenerator,
       TypeAndLocalInfoSupplier info) {
@@ -143,8 +143,8 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+  boolean internalIsMaterializableInContext(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index 6e69356..f4ce6ae 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -63,7 +63,7 @@
 
   @Override
   public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       ProgramMethod context,
       NumberGenerator valueNumberGenerator,
       TypeAndLocalInfoSupplier info) {
@@ -84,8 +84,8 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+  boolean internalIsMaterializableInContext(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 390ac7b..0f0ff90 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -37,20 +37,27 @@
    * #isMaterializableInContext}.
    */
   public final Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      IRCode code,
-      TypeAndLocalInfoSupplier info) {
+      AppView<?> appView, IRCode code, TypeAndLocalInfoSupplier info) {
     return createMaterializingInstruction(appView, code.context(), code.valueNumberGenerator, info);
   }
 
   public abstract Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       ProgramMethod context,
       NumberGenerator valueNumberGenerator,
       TypeAndLocalInfoSupplier info);
 
-  public abstract boolean isMaterializableInContext(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context);
+  public final boolean isMaterializableInContext(AppView<?> appView, ProgramMethod context) {
+    if (appView.enableWholeProgramOptimizations()) {
+      assert appView.hasClassHierarchy();
+      return internalIsMaterializableInContext(appView.withClassHierarchy(), context);
+    }
+    // All abstract values created in D8 should be accessible in all contexts.
+    return true;
+  }
+
+  abstract boolean internalIsMaterializableInContext(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context);
 
   public abstract boolean isMaterializableInAllContexts(AppView<AppInfoWithLiveness> appView);
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index b9f07e3..bdbe697 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.optimize.NestUtils;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.google.common.collect.ImmutableList;
@@ -331,10 +330,7 @@
 
   @Override
   public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
-      AppView<AppInfoWithLiveness> appView,
-      IRCode code,
-      DexType type,
-      Consumer<InitClass> consumer) {
+      AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer) {
     Instruction toBeReplaced = current;
     assert toBeReplaced != null;
     assert toBeReplaced.isStaticFieldInstruction() || toBeReplaced.isInvokeStatic();
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 552ebf3..3551561 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -83,9 +84,9 @@
     }
     SingleFieldResolutionResult<?> singleFieldResolutionResult =
         resolutionResult.asSingleFieldResolutionResult();
-    DexEncodedField resolvedField = singleFieldResolutionResult.getResolvedField();
+    DexClassAndField resolvedField = singleFieldResolutionResult.getResolutionPair();
     // Check if the instruction may fail with an IncompatibleClassChangeError.
-    if (resolvedField.isStatic() != isStaticFieldInstruction()) {
+    if (resolvedField.getAccessFlags().isStatic() != isStaticFieldInstruction()) {
       return true;
     }
     // Check if the resolution target is accessible.
@@ -115,11 +116,8 @@
         isStaticFieldInstruction() && !assumption.canAssumeClassIsAlreadyInitialized();
     if (mayTriggerClassInitialization) {
       // Only check for <clinit> side effects if there is no -assumenosideeffects rule.
-      if (appView.appInfo().hasLiveness()) {
-        AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
-        if (appInfoWithLiveness.noSideEffects.containsKey(resolvedField.getReference())) {
-          return false;
-        }
+      if (appView.getAssumeInfoCollection().isSideEffectFree(resolvedField)) {
+        return false;
       }
       // May trigger <clinit> that may have side effects.
       if (field.holder.classInitializationMayHaveSideEffectsInContext(appView, context)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 9125869..4962981 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ListIterator;
 import java.util.NoSuchElementException;
@@ -63,10 +62,7 @@
 
   @Override
   public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
-      AppView<AppInfoWithLiveness> appView,
-      IRCode code,
-      DexType type,
-      Consumer<InitClass> consumer) {
+      AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer) {
     return instructionIterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
         appView, code, type, consumer);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 5055072..e13fe25 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
@@ -111,16 +110,13 @@
   boolean replaceCurrentInstructionByNullCheckIfPossible(AppView<?> appView, ProgramMethod context);
 
   default boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
-      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+      AppView<?> appView, IRCode code, DexType type) {
     return removeOrReplaceCurrentInstructionByInitClassIfPossible(
         appView, code, type, ConsumerUtils.emptyConsumer());
   }
 
   boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
-      AppView<AppInfoWithLiveness> appView,
-      IRCode code,
-      DexType type,
-      Consumer<InitClass> consumer);
+      AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer);
 
   void replaceCurrentInstructionWithConstClass(
       AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 668d515..f5e7b03 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -216,12 +216,10 @@
     }
 
     DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
-    if (appView.hasLiveness()) {
-      if (appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(getInvokedMethod())
-          || appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(resolvedMethod)) {
+    if (appView.getAssumeInfoCollection().isSideEffectFree(getInvokedMethod())
+        || appView.getAssumeInfoCollection().isSideEffectFree(resolvedMethod)) {
         return false;
       }
-    }
 
     // Find the target and check if the invoke may have side effects.
     DexClassAndMethod singleTarget = lookupSingleTarget(appView, context);
@@ -237,10 +235,8 @@
     }
 
     // Verify that the target method does not have side-effects.
-    if (appView.hasLiveness()) {
-      if (appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(singleTarget)) {
-        return false;
-      }
+    if (appView.getAssumeInfoCollection().isSideEffectFree(singleTarget)) {
+      return false;
     }
 
     DexEncodedMethod singleTargetDefinition = singleTarget.getDefinition();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 8f47b31..9e6a918 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -220,7 +220,7 @@
     }
 
     // Verify that the target method does not have side-effects.
-    if (appViewWithLiveness.appInfo().isAssumeNoSideEffectsMethod(singleTarget)) {
+    if (appView.getAssumeInfoCollection().isSideEffectFree(singleTarget)) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 093faf1..91b51be 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
@@ -87,10 +86,7 @@
 
   @Override
   public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
-      AppView<AppInfoWithLiveness> appView,
-      IRCode code,
-      DexType type,
-      Consumer<InitClass> consumer) {
+      AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer) {
     return currentBlockIterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
         appView, code, type, consumer);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index a078dde..cdec1d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -154,6 +154,15 @@
   }
 
   @Override
+  public boolean instructionInstanceCanThrow(
+      AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
+    if (appView.getAssumeInfoCollection().isSideEffectFree(getField())) {
+      return false;
+    }
+    return super.instructionInstanceCanThrow(appView, context, assumption);
+  }
+
+  @Override
   public int maxInValueRegister() {
     return Constants.U8BIT_MAX;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index bb83120..54710df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
@@ -940,8 +941,8 @@
     return isThis;
   }
 
-  public void setValueRange(LongInterval range) {
-    valueRange = range;
+  public void setValueRange(NumberFromIntervalValue range) {
+    valueRange = new LongInterval(range.getMinInclusive(), range.getMaxInclusive());
   }
 
   public boolean hasValueRange() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 201c789..ec93ef2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -55,6 +55,7 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.Position;
@@ -351,11 +352,10 @@
         && currentBlock.getPredecessors().get(0) == previousBlock;
   }
 
-  private static void removeTrivialFallthroughBlocks(IRCode code) {
-    for (int blockIndex = 1; blockIndex < code.blocks.size() - 1; blockIndex++) {
-      // We skip the last block as it has no fallthrough. We also skip checking the entry block as
-      // it has no predecessors and must define the initial position. Any subsequent block must be
-      // statically reachable and thus have predecessors.
+  private static void removeTrivialGotoBlocks(IRCode code) {
+    for (int blockIndex = 1; blockIndex < code.blocks.size(); blockIndex++) {
+      // We skip checking the entry block as it has no predecessors and must define the initial
+      // position. Any subsequent block must be statically reachable and thus have predecessors.
       BasicBlock currentBlock = code.blocks.get(blockIndex);
       assert !currentBlock.getPredecessors().isEmpty();
       if (currentBlock.size() != 2) {
@@ -366,35 +366,64 @@
       if (debugPosition == null || exit == null || debugPosition.getPosition().isNone()) {
         continue;
       }
-      BasicBlock nextBlock = code.blocks.get(blockIndex + 1);
-      if (exit.getTarget() != nextBlock) {
-        continue;
-      }
-      // The block is a trivial position block that falls through to the following.
-      // If the position is equal to each predecessor block then the line is already active on
-      // each entry to the fallthrough and it can be safely removed.
       boolean allMatch = true;
       Position position = debugPosition.getPosition();
       for (BasicBlock pred : currentBlock.getPredecessors()) {
-        // Do to the target == next check this cannot be a trivial loop.
-        assert pred != currentBlock;
+        // If the block is a trivial loop it must remain.
+        if (pred == currentBlock) {
+          allMatch = false;
+          break;
+        }
+        // If the position is already active on each predecessor exit it can be safely removed
+        // (except for if/switch fallthrough cases guarded below).
         Position predExit = pred.exit().getPosition();
         if (!position.equals(predExit)) {
           allMatch = false;
           break;
         }
+        // If this is a required fallthrough, we can only remove it if it targets the next block.
+        // Note that this could fail for a given block but then become valid after an intermediate
+        // block is removed. See the reset of blockIndex below for dealing with that case.
+        if (isFallthroughTargetToNonFallthroughTarget(pred, currentBlock, blockIndex, code)) {
+          allMatch = false;
+          break;
+        }
       }
       if (allMatch) {
         currentBlock.removeInstruction(debugPosition);
-        CodeRewriter.unlinkTrivialGotoBlock(currentBlock, nextBlock);
+        CodeRewriter.unlinkTrivialGotoBlock(currentBlock, exit.getTarget());
         code.removeBlocks(Collections.singleton(currentBlock));
         // Having removed the block at blockIndex, the previous block may now be a trivial
-        // fallthrough. Rewind to that point and retry. This avoids iterating to a fixed point.
+        // fallthrough from an if/switch. Rewind to that point and retry. This avoids iterating to
+        // a fixed point.
         blockIndex = Math.max(0, blockIndex - 2);
       }
     }
   }
 
+  private static boolean isFallthroughTargetToNonFallthroughTarget(
+      BasicBlock pred, BasicBlock current, int blockIndex, IRCode code) {
+    JumpInstruction exit = pred.exit();
+    BasicBlock fallthrough;
+    if (exit.isIf()) {
+      fallthrough = exit.asIf().fallthroughBlock();
+    } else if (exit.isSwitch()) {
+      fallthrough = exit.asSwitch().fallthroughBlock();
+    } else {
+      return false;
+    }
+    if (fallthrough != current) {
+      return false;
+    }
+    if (blockIndex + 1 >= code.blocks.size()) {
+      // The current block is a if/switch fallthrough target and there is no next-block.
+      // The current jump is thus to a non-fallthrough block.
+      return true;
+    }
+    BasicBlock nextBlock = code.blocks.get(blockIndex + 1);
+    return current.exit().asGoto().getTarget() != nextBlock;
+  }
+
   // Eliminates unneeded debug positions.
   //
   // After this pass all remaining debug positions mark places where we must ensure a materializing
@@ -407,7 +436,7 @@
     // We must start by removing any blocks that are already trivial fallthrough blocks with no
     // position change. With these removed it is then sound to make the fallthrough judgement when
     // determining if a goto will materialize or not.
-    removeTrivialFallthroughBlocks(code);
+    removeTrivialGotoBlocks(code);
 
     // Current position known to have a materializing instruction associated with it.
     Position currentMaterializedPosition = Position.none();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 0c255da..f94cde1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.AssumeInserter;
+import com.android.tools.r8.ir.optimize.CheckNotNullConverter;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -61,7 +62,6 @@
 import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.MemberValuePropagation;
 import com.android.tools.r8.ir.optimize.NaturalIntLoopRemover;
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
@@ -76,6 +76,9 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.D8MemberValuePropagation;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.MemberValuePropagation;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.R8MemberValuePropagation;
 import com.android.tools.r8.ir.optimize.outliner.Outliner;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
@@ -128,7 +131,7 @@
   public final CodeRewriter codeRewriter;
   private final NaturalIntLoopRemover naturalIntLoopRemover = new NaturalIntLoopRemover();
   private final ConstantCanonicalizer constantCanonicalizer;
-  public final MemberValuePropagation memberValuePropagation;
+  public final MemberValuePropagation<?> memberValuePropagation;
   private final LensCodeRewriter lensCodeRewriter;
   private final Inliner inliner;
   private final IdentifierNameStringMarker identifierNameStringMarker;
@@ -256,7 +259,7 @@
       this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
       this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
       this.outliner = Outliner.create(appViewWithLiveness);
-      this.memberValuePropagation = new MemberValuePropagation(appViewWithLiveness);
+      this.memberValuePropagation = new R8MemberValuePropagation(appViewWithLiveness);
       this.methodOptimizationInfoCollector =
           new MethodOptimizationInfoCollector(appViewWithLiveness, this);
       // TODO(b/214496607): Enable open/closed interfaces analysis.
@@ -276,6 +279,7 @@
       this.enumValueOptimizer =
           options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
     } else {
+      AppView<AppInfo> appViewWithoutClassHierarchy = appView.withoutClassHierarchy();
       this.assumeInserter = null;
       this.classInliner = null;
       this.dynamicTypeOptimization = null;
@@ -283,7 +287,10 @@
       this.libraryMethodOverrideAnalysis = null;
       this.inliner = null;
       this.outliner = Outliner.empty();
-      this.memberValuePropagation = null;
+      this.memberValuePropagation =
+          options.isGeneratingDex()
+              ? new D8MemberValuePropagation(appViewWithoutClassHierarchy)
+              : null;
       this.lensCodeRewriter = null;
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
@@ -734,6 +741,7 @@
 
     outliner.rewriteWithLens();
     enumUnboxer.unboxEnums(appView, this, postMethodProcessorBuilder, executorService, feedback);
+    appView.unboxedEnums().checkEnumsUnboxed(appView);
 
     GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens();
 
@@ -1176,6 +1184,7 @@
     }
 
     assertionsRewriter.run(method, code, deadCodeRemover, timing);
+    CheckNotNullConverter.runIfNecessary(appView, code);
 
     if (serviceLoaderRewriter != null) {
       assert appView.appInfo().hasLiveness();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 9c220f6..5011171 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
@@ -310,6 +311,7 @@
       DexString name;
       DexProto proto;
       DexMethod method;
+      DexClassAndMethod definition;
 
       // Objects
       type = factory.objectsType;
@@ -351,14 +353,17 @@
       addProvider(new InvokeRewriter(method, ObjectsMethodRewrites.rewriteRequireNonNull()));
 
       // T Objects.requireNonNull(T obj, String message)
-      name = factory.createString("requireNonNull");
-      proto = factory.createProto(factory.objectType, factory.objectType, factory.stringType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(
-          new MethodGenerator(
-              method,
-              BackportedMethods::ObjectsMethods_requireNonNullMessage,
-              "requireNonNullMessage"));
+      method = factory.objectsMethods.requireNonNullWithMessage;
+      definition = appView.enableWholeProgramOptimizations() ? appView.definitionFor(method) : null;
+      // Only backport requireNonNull(Object, String) if it is not matched by -convertchecknotnull.
+      // Otherwise all calls will be rewritten to getClass().
+      if (definition == null || !definition.getOptimizationInfo().isConvertCheckNotNull()) {
+        addProvider(
+            new MethodGenerator(
+                method,
+                BackportedMethods::ObjectsMethods_requireNonNullMessage,
+                "requireNonNullMessage"));
+      }
 
       // String Objects.toString(Object o)
       name = factory.createString("toString");
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 0d0dd97..5d774b2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -260,7 +260,12 @@
     }
   }
 
-  private boolean isCovariantReturnTypeAnnotation(DexEncodedAnnotation annotation) {
+  public boolean isCovariantReturnTypeAnnotation(DexEncodedAnnotation annotation) {
+    return isCovariantReturnTypeAnnotation(annotation, factory);
+  }
+
+  public static boolean isCovariantReturnTypeAnnotation(
+      DexEncodedAnnotation annotation, DexItemFactory factory) {
     return isCovariantReturnTypeAnnotation(annotation.type, factory);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
index a6384e1..b288d9a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.DesugarDescription.ScanCallback;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
@@ -105,46 +106,46 @@
                 getThrowInstructions(
                     appView,
                     invoke,
-                    resolutionResult,
                     localStackAllocator,
                     eventConsumer,
                     context,
-                    methodProcessingContext))
+                    methodProcessingContext,
+                    getMethodSynthesizerForThrowing(appView, invoke, resolutionResult, context)))
         .build();
   }
 
+  public static DesugarDescription computeInvokeAsThrowNSMERewrite(
+      AppView<?> appView, CfInvoke invoke, ScanCallback scanCallback) {
+    DesugarDescription.Builder builder =
+        DesugarDescription.builder()
+            .setDesugarRewrite(
+                (freshLocalProvider,
+                    localStackAllocator,
+                    eventConsumer,
+                    context,
+                    methodProcessingContext,
+                    dexItemFactory) ->
+                    getThrowInstructions(
+                        appView,
+                        invoke,
+                        localStackAllocator,
+                        eventConsumer,
+                        context,
+                        methodProcessingContext,
+                        UtilityMethodsForCodeOptimizations
+                            ::synthesizeThrowNoSuchMethodErrorMethod));
+    builder.addScanEffect(scanCallback);
+    return builder.build();
+  }
+
   private static Collection<CfInstruction> getThrowInstructions(
       AppView<?> appView,
       CfInvoke invoke,
-      MethodResolutionResult resolutionResult,
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
-    MethodSynthesizerConsumer methodSynthesizerConsumer = null;
-    if (resolutionResult == null) {
-      methodSynthesizerConsumer =
-          UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
-    } else if (resolutionResult.isSingleResolution()) {
-      if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
-      }
-    } else if (resolutionResult.isFailedResolution()) {
-      FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
-      AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
-      if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
-      } else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
-      } else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
-        methodSynthesizerConsumer =
-            UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
-      }
-    }
-
+      MethodProcessingContext methodProcessingContext,
+      MethodSynthesizerConsumer methodSynthesizerConsumer) {
     if (methodSynthesizerConsumer == null) {
       assert false;
       return null;
@@ -198,4 +199,32 @@
     }
     return replacement;
   }
+
+  private static MethodSynthesizerConsumer getMethodSynthesizerForThrowing(
+      AppView<?> appView,
+      CfInvoke invoke,
+      MethodResolutionResult resolutionResult,
+      ProgramMethod context) {
+    if (resolutionResult == null) {
+      return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+    } else if (resolutionResult.isSingleResolution()) {
+      if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
+        return UtilityMethodsForCodeOptimizations
+            ::synthesizeThrowIncompatibleClassChangeErrorMethod;
+      }
+    } else if (resolutionResult.isFailedResolution()) {
+      FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
+      AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+      if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
+        return UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
+      } else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
+        return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+      } else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
+        return UtilityMethodsForCodeOptimizations
+            ::synthesizeThrowIncompatibleClassChangeErrorMethod;
+      }
+    }
+
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
index 695af34..c764377 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -78,6 +78,7 @@
             emulatedInterface.getSourceFile(),
             null,
             Collections.emptyList(),
+            Collections.emptyList(),
             null, // Note that we clear the enclosing and inner class attributes.
             Collections.emptyList(),
             emulatedInterface.getClassSignature(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index bc8ebd0..8e8541b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.collect.Iterables;
@@ -711,7 +712,23 @@
       }
     }
 
-    return computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
+    DesugarDescription emulatedInterfaceDesugaring =
+        computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
+    if (!emulatedInterfaceDesugaring.needsDesugaring() && context.isDefaultMethod()) {
+      return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowNSMERewrite(
+          appView,
+          invoke,
+          () ->
+              appView
+                  .reporter()
+                  .warning(
+                      new StringDiagnostic(
+                          "Interface method desugaring has inserted NoSuchMethodError replacing a"
+                              + " super call in "
+                              + context.toSourceString(),
+                          context.getOrigin())));
+    }
+    return emulatedInterfaceDesugaring;
   }
 
   private DesugarDescription computeEmulatedInterfaceInvokeSpecial(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index df9d508..ed206d4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -240,9 +240,7 @@
     if (invoke.hasUsedOutValue() && invoke.getOutType().isReferenceType()) {
       AssumeInfo assumeInfo =
           AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget);
-      if (assumeInfo != null
-          && assumeInfo.hasReturnInfo()
-          && assumeInfo.getReturnInfo().isNonNull()) {
+      if (assumeInfo.getAssumeType().getNullability().isDefinitelyNotNull()) {
         assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, invoke.outValue());
       }
     }
@@ -294,10 +292,8 @@
     DexClassAndField field = resolutionResult.getResolutionPair();
 
     if (field.getType().isReferenceType()) {
-      AssumeInfo assumeInfo = AssumeInfoLookup.lookupAssumeInfo(appView, field);
-      if (assumeInfo != null
-          && assumeInfo.hasReturnInfo()
-          && assumeInfo.getReturnInfo().isNonNull()) {
+      AssumeInfo assumeInfo = appView.getAssumeInfoCollection().get(field);
+      if (assumeInfo.getAssumeType().getNullability().isDefinitelyNotNull()) {
         assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(fieldGet, fieldGet.outValue());
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java b/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java
new file mode 100644
index 0000000..9cffada
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, 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;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+
+public class CheckNotNullConverter {
+
+  public static void runIfNecessary(AppView<?> appView, IRCode code) {
+    if (appView.enableWholeProgramOptimizations()) {
+      run(appView.withClassHierarchy(), code);
+    }
+  }
+
+  /**
+   * Replace all calls to methods marked as a check-not-null method by a call to Object.getClass(),
+   * using the first argument as the receiver for the new call.
+   *
+   * <p>If the invoke has an out-value, the out-value is replaced by the first argument to allow
+   * removing the invoke.
+   */
+  private static void run(AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code) {
+    BasicBlockIterator blockIterator = code.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator instructionIterator = block.listIterator(code);
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        if (instruction.isInvokeMethod()) {
+          rewriteInvoke(appView, code, instructionIterator, instruction.asInvokeMethod());
+        }
+      }
+    }
+  }
+
+  private static void rewriteInvoke(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke) {
+    ProgramMethod context = code.context();
+    DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
+    if (singleTarget == null || !singleTarget.getOptimizationInfo().isConvertCheckNotNull()) {
+      return;
+    }
+    Value checkNotNullValue = invoke.getFirstNonReceiverArgument();
+    if (invoke.hasUsedOutValue()) {
+      invoke.outValue().replaceUsers(checkNotNullValue);
+    }
+    if (checkNotNullValue.getType().nullability().isDefinitelyNotNull()) {
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    } else {
+      instructionIterator.replaceCurrentInstructionWithNullCheck(appView, checkNotNullValue);
+    }
+  }
+}
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 107bd20..75c275f 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
@@ -43,6 +43,7 @@
 import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -339,16 +340,16 @@
       SingleResolutionResult<?> resolutionResult,
       ProgramMethod singleTarget,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod singleTargetReference = singleTarget.getReference();
     if (!appView.getKeepInfo(singleTarget).isInliningAllowed(appView.options())) {
       whyAreYouNotInliningReporter.reportPinned();
       return true;
     }
 
-    if (appInfo.noSideEffects.containsKey(invoke.getInvokedMethod())
-        || appInfo.noSideEffects.containsKey(resolutionResult.getResolvedMethod().getReference())
-        || appInfo.noSideEffects.containsKey(singleTargetReference)) {
+    AssumeInfoCollection assumeInfoCollection = appView.getAssumeInfoCollection();
+    if (assumeInfoCollection.isSideEffectFree(invoke.getInvokedMethod())
+        || assumeInfoCollection.isSideEffectFree(resolutionResult.getResolutionPair())
+        || assumeInfoCollection.isSideEffectFree(singleTargetReference)) {
       return !singleTarget.getDefinition().getOptimizationInfo().forceInline();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 4be71a5..f0980f3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -58,6 +58,7 @@
 import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.R8MemberValuePropagation;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.ConsumerUtils;
@@ -1215,7 +1216,7 @@
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> inlineeBlocks) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    new MemberValuePropagation(appView)
+    new R8MemberValuePropagation(appView)
         .run(code, blockIterator, affectedValues, inlineeBlocks::contains);
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
index 264fc7b..00c4f50 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
@@ -4,13 +4,18 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import com.android.tools.r8.errors.CheckEnumUnboxedDiagnostic;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 public class EnumDataMap {
@@ -24,6 +29,23 @@
     this.map = map;
   }
 
+  public void checkEnumsUnboxed(AppView<AppInfoWithLiveness> appView) {
+    List<DexProgramClass> failed = new ArrayList<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.isEnum()) {
+        if (appView.getKeepInfo(clazz).isCheckEnumUnboxedEnabled(appView.options())
+            && !isUnboxedEnum(clazz)) {
+          failed.add(clazz);
+        }
+      }
+    }
+    if (!failed.isEmpty()) {
+      CheckEnumUnboxedDiagnostic diagnostic =
+          CheckEnumUnboxedDiagnostic.builder().addFailedEnums(failed).build();
+      throw appView.reporter().fatalError(diagnostic);
+    }
+  }
+
   public boolean isUnboxedEnum(DexProgramClass clazz) {
     return isUnboxedEnum(clazz.getType());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index c9f6819..f518d5e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -147,6 +147,11 @@
   }
 
   @Override
+  public boolean isConvertCheckNotNull() {
+    return false;
+  }
+
+  @Override
   public boolean isInitializerEnablingJavaVmAssertions() {
     return UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index c36ee4d..c4e8e3a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -68,6 +68,8 @@
 
   public abstract InstanceInitializerInfo getInstanceInitializerInfo(InvokeDirect invoke);
 
+  public abstract boolean isConvertCheckNotNull();
+
   public abstract boolean isInitializerEnablingJavaVmAssertions();
 
   public abstract AbstractValue getAbstractReturnValue();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index df68f40..3491800 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -44,6 +44,7 @@
       DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
   private ClassInlinerMethodConstraint classInlinerConstraint =
       ClassInlinerMethodConstraint.alwaysFalse();
+  private boolean convertCheckNotNull = false;
   private EnumUnboxerMethodClassification enumUnboxerMethodClassification =
       EnumUnboxerMethodClassification.unknown();
   private DynamicType dynamicType = DynamicType.unknown();
@@ -266,6 +267,10 @@
     this.classInlinerConstraint = ClassInlinerMethodConstraint.alwaysFalse();
   }
 
+  void setConvertCheckNotNull() {
+    this.convertCheckNotNull = true;
+  }
+
   @Override
   public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
     return enumUnboxerMethodClassification;
@@ -454,6 +459,11 @@
   }
 
   @Override
+  public boolean isConvertCheckNotNull() {
+    return convertCheckNotNull;
+  }
+
+  @Override
   public boolean isInitializerEnablingJavaVmAssertions() {
     return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 7a6a3ce..c766497 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.info;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
@@ -152,6 +153,10 @@
     // Ignored.
   }
 
+  public void setConvertCheckNotNull(DexClassAndMethod method) {
+    method.getDefinition().getMutableOptimizationInfo().setConvertCheckNotNull();
+  }
+
   @Override
   public void setEnumUnboxerMethodClassification(
       ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
index 37ca53d..2cf8495 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -110,51 +110,67 @@
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues,
       Set<BasicBlock> blocksToRemove) {
+    // Replace Android logging statements like Log.w(...) and Log.IsLoggable(..., WARNING) at or
+    // below a certain logging level by false.
+    int logLevel = getLogLevel(invoke, singleTarget);
     int maxRemovedAndroidLogLevel =
         appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel();
-    if (singleTarget.getReference() == isLoggableMethod) {
-      Value logLevelValue = invoke.arguments().get(1).getAliasedValue();
-      if (!logLevelValue.isPhi() && !logLevelValue.hasLocalInfo()) {
-        Instruction definition = logLevelValue.definition;
-        if (definition.isConstNumber()) {
-          int logLevel = definition.asConstNumber().getIntValue();
-          replaceInvokeWithConstNumber(
-              code, instructionIterator, invoke, maxRemovedAndroidLogLevel >= logLevel ? 0 : 1);
-        }
-      }
-    } else if (singleTarget.getReference() == vMethod) {
-      if (maxRemovedAndroidLogLevel >= VERBOSE) {
-        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
-      }
-    } else if (singleTarget.getReference() == dMethod) {
-      if (maxRemovedAndroidLogLevel >= DEBUG) {
-        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
-      }
-    } else if (singleTarget.getReference() == iMethod) {
-      if (maxRemovedAndroidLogLevel >= INFO) {
-        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
-      }
-    } else if (singleTarget.getReference() == wMethod) {
-      if (maxRemovedAndroidLogLevel >= WARN) {
-        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
-      }
-    } else if (singleTarget.getReference() == eMethod) {
-      if (maxRemovedAndroidLogLevel >= ERROR) {
-        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
-      }
-    } else if (singleTarget.getReference() == wtfMethod) {
-      if (maxRemovedAndroidLogLevel >= ASSERT) {
-        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
-      }
+    if (VERBOSE <= logLevel && logLevel <= maxRemovedAndroidLogLevel) {
+      instructionIterator.replaceCurrentInstructionWithConstFalse(code);
     }
   }
 
-  private void replaceInvokeWithConstNumber(
-      IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, int value) {
-    if (invoke.hasOutValue() && invoke.outValue().hasAnyUsers()) {
-      instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
-    } else {
-      instructionIterator.removeOrReplaceByDebugLocalRead();
+  /**
+   * @return The log level of the given invoke if it is a call to an android.util.Log method and the
+   *     log level can be determined, otherwise returns -1.
+   */
+  private int getLogLevel(InvokeMethod invoke, DexClassAndMethod singleTarget) {
+    DexMethod singleTargetReference = singleTarget.getReference();
+    switch (singleTargetReference.getName().getFirstByteAsChar()) {
+      case 'd':
+        if (singleTargetReference == dMethod) {
+          return DEBUG;
+        }
+        break;
+      case 'e':
+        if (singleTargetReference == eMethod) {
+          return ERROR;
+        }
+        break;
+      case 'i':
+        if (singleTargetReference == iMethod) {
+          return INFO;
+        }
+        if (singleTargetReference == isLoggableMethod) {
+          Value logLevelValue = invoke.arguments().get(1).getAliasedValue();
+          if (!logLevelValue.isPhi() && !logLevelValue.hasLocalInfo()) {
+            Instruction definition = logLevelValue.getDefinition();
+            if (definition.isConstNumber()) {
+              int logLevel = definition.asConstNumber().getIntValue();
+              if (VERBOSE <= logLevel && logLevel <= ASSERT) {
+                return logLevel;
+              }
+              assert false;
+            }
+          }
+        }
+        break;
+      case 'v':
+        if (singleTargetReference == vMethod) {
+          return VERBOSE;
+        }
+        break;
+      case 'w':
+        if (singleTargetReference == wMethod) {
+          return WARN;
+        }
+        if (singleTargetReference == wtfMethod) {
+          return ASSERT;
+        }
+        break;
+      default:
+        break;
     }
+    return -1;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
index bd7edf7..0c65d04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
@@ -37,14 +37,14 @@
       return false;
     }
 
+    // Check if there is an -assumenosideeffects rule for the toString() method.
+    if (appView.getAssumeInfoCollection().isSideEffectFree(toStringMethodReference)) {
+      return false;
+    }
+
     if (appView.appInfo().hasLiveness()) {
       AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
 
-      // Check if there is an -assumenosideeffects rule for the toString() method.
-      if (appInfo.isAssumeNoSideEffectsMethod(toStringMethodReference)) {
-        return false;
-      }
-
       // Check if this is a program class with a toString() method that does not have side effects.
       DexClass clazz = appInfo.definitionFor(classType);
       if (clazz != null && clazz.isEffectivelyFinal(appView)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java
new file mode 100644
index 0000000..020d86e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, 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.membervaluepropagation;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import java.util.ListIterator;
+import java.util.Set;
+
+public class D8MemberValuePropagation extends MemberValuePropagation<AppInfo> {
+
+  public D8MemberValuePropagation(AppView<AppInfo> appView) {
+    super(appView);
+  }
+
+  @Override
+  void rewriteArrayGet(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      ArrayGet arrayGet) {
+    // Intentionally empty.
+  }
+
+  @Override
+  void rewriteInstanceGet(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      InstanceGet current) {
+    // Intentionally empty.
+  }
+
+  @Override
+  void rewriteInstancePut(IRCode code, InstructionListIterator iterator, InstancePut current) {
+    // Intentionally empty.
+  }
+
+  @Override
+  void rewriteInvokeMethod(
+      IRCode code,
+      ProgramMethod context,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      InvokeMethod invoke) {
+    // Intentionally empty.
+  }
+
+  @Override
+  void rewriteStaticGet(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      StaticGet current) {
+    AssumeInfo assumeInfo = appView.getAssumeInfoCollection().get(current.getField());
+    applyAssumeInfo(code, affectedValues, blocks, iterator, current, assumeInfo);
+  }
+
+  @Override
+  void rewriteStaticPut(IRCode code, InstructionListIterator iterator, StaticPut current) {
+    // Intentionally empty.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java
new file mode 100644
index 0000000..14a54b1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java
@@ -0,0 +1,234 @@
+// Copyright (c) 2022, 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.membervaluepropagation;
+
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
+import static com.google.common.base.Predicates.alwaysTrue;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.utils.IteratorUtils;
+import com.google.common.collect.Sets;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.function.Predicate;
+
+public abstract class MemberValuePropagation<T extends AppInfo> {
+
+  final AppView<T> appView;
+
+  MemberValuePropagation(AppView<T> appView) {
+    this.appView = appView;
+  }
+
+  /**
+   * Replace invoke targets and field accesses with constant values where possible.
+   *
+   * <p>Also assigns value ranges to values where possible.
+   */
+  public void run(IRCode code) {
+    IRMetadata metadata = code.metadata();
+    if (!metadata.mayHaveFieldInstruction() && !metadata.mayHaveInvokeMethod()) {
+      return;
+    }
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    run(code, code.listIterator(), affectedValues, alwaysTrue());
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+    assert code.isConsistentSSA(appView);
+    assert code.verifyTypes(appView);
+  }
+
+  public void run(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Set<Value> affectedValues,
+      Predicate<BasicBlock> blockTester) {
+    ProgramMethod context = code.context();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      if (!blockTester.test(block)) {
+        continue;
+      }
+      InstructionListIterator iterator = block.listIterator(code);
+      while (iterator.hasNext()) {
+        Instruction current = iterator.next();
+        switch (current.opcode()) {
+          case ARRAY_GET:
+            rewriteArrayGet(code, affectedValues, blockIterator, iterator, current.asArrayGet());
+            break;
+          case INSTANCE_GET:
+            rewriteInstanceGet(
+                code, affectedValues, blockIterator, iterator, current.asInstanceGet());
+            break;
+          case INSTANCE_PUT:
+            rewriteInstancePut(code, iterator, current.asInstancePut());
+            break;
+          case INVOKE_DIRECT:
+          case INVOKE_INTERFACE:
+          case INVOKE_STATIC:
+          case INVOKE_SUPER:
+          case INVOKE_VIRTUAL:
+            rewriteInvokeMethod(
+                code, context, affectedValues, blockIterator, iterator, current.asInvokeMethod());
+            break;
+          case STATIC_GET:
+            rewriteStaticGet(code, affectedValues, blockIterator, iterator, current.asStaticGet());
+            break;
+          case STATIC_PUT:
+            rewriteStaticPut(code, iterator, current.asStaticPut());
+            break;
+          default:
+            break;
+        }
+      }
+    }
+  }
+
+  abstract void rewriteArrayGet(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      ArrayGet arrayGet);
+
+  abstract void rewriteInstanceGet(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      InstanceGet current);
+
+  abstract void rewriteInstancePut(
+      IRCode code, InstructionListIterator iterator, InstancePut current);
+
+  abstract void rewriteInvokeMethod(
+      IRCode code,
+      ProgramMethod context,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      InvokeMethod invoke);
+
+  abstract void rewriteStaticGet(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      StaticGet current);
+
+  abstract void rewriteStaticPut(IRCode code, InstructionListIterator iterator, StaticPut current);
+
+  boolean applyAssumeInfo(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      Instruction current,
+      AssumeInfo assumeInfo) {
+    // Remove if side effect free.
+    if (assumeInfo.isSideEffectFree() && current.hasUnusedOutValue()) {
+      iterator.removeOrReplaceByDebugLocalRead();
+      return true;
+    }
+
+    // Set value range if any.
+    if (current.hasOutValue()
+        && current.getOutType().isPrimitiveType()
+        && assumeInfo.getAssumeValue().isNumberFromIntervalValue()) {
+      current.outValue().setValueRange(assumeInfo.getAssumeValue().asNumberFromIntervalValue());
+    }
+
+    // Insert replacement if any.
+    Instruction replacement = createReplacementFromAssumeInfo(assumeInfo, code, current);
+    if (replacement == null) {
+      return false;
+    }
+
+    affectedValues.addAll(current.outValue().affectedValues());
+    if (assumeInfo.isSideEffectFree()) {
+      iterator.replaceCurrentInstruction(replacement);
+      return true;
+    }
+    BasicBlock block = current.getBlock();
+    Position position = current.getPosition();
+    if (current.hasOutValue()) {
+      assert replacement.outValue() != null;
+      current.outValue().replaceUsers(replacement.outValue());
+    }
+    if (current.isInstanceGet()) {
+      iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
+    } else if (current.isStaticGet()) {
+      StaticGet staticGet = current.asStaticGet();
+      iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
+          appView, code, staticGet.getField().holder);
+    }
+    replacement.setPosition(position);
+    if (block.hasCatchHandlers()) {
+      BasicBlock splitBlock = iterator.split(code, blocks);
+      splitBlock.listIterator(code).add(replacement);
+
+      // Process the materialized value.
+      blocks.previous();
+      assert !iterator.hasNext();
+      assert IteratorUtils.peekNext(blocks) == splitBlock;
+
+      return true;
+    } else {
+      iterator.add(replacement);
+    }
+
+    // Process the materialized value.
+    iterator.previous();
+    assert iterator.peekNext() == replacement;
+
+    return true;
+  }
+
+  private Instruction createReplacementFromAssumeInfo(
+      AssumeInfo assumeInfo, IRCode code, Instruction instruction) {
+    if (assumeInfo.getAssumeValue().isUnknown()) {
+      return null;
+    }
+
+    AbstractValue assumeValue = assumeInfo.getAssumeValue();
+    if (assumeValue.isSingleValue()) {
+      SingleValue singleValue = assumeValue.asSingleValue();
+      if (singleValue.isMaterializableInContext(appView, code.context())) {
+        return singleValue.createMaterializingInstruction(appView, code, instruction);
+      }
+    }
+
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
similarity index 61%
rename from src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
rename to src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
index d572d2f..bc33a3a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
@@ -1,12 +1,8 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, 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;
+package com.android.tools.r8.ir.optimize.membervaluepropagation;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.google.common.base.Predicates.alwaysTrue;
-
-import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -18,7 +14,6 @@
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -27,7 +22,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -41,33 +36,20 @@
 import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
 import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
-import com.android.tools.r8.utils.IteratorUtils;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.google.common.collect.Sets;
 import java.util.ListIterator;
 import java.util.Set;
-import java.util.function.Predicate;
 
-public class MemberValuePropagation {
+public class R8MemberValuePropagation extends MemberValuePropagation<AppInfoWithLiveness> {
 
   private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
 
-  private final AppView<AppInfoWithLiveness> appView;
-  private final Reporter reporter;
-
-  // Fields for which we have reported warnings to due Proguard configuration rules.
-  private final Set<DexField> warnedFields = Sets.newIdentityHashSet();
-
-  public MemberValuePropagation(AppView<AppInfoWithLiveness> appView) {
-    this.appView = appView;
-    this.reporter = appView.options().reporter;
+  public R8MemberValuePropagation(AppView<AppInfoWithLiveness> appView) {
+    super(appView);
   }
 
-  private void rewriteArrayGet(
+  @Override
+  void rewriteArrayGet(
       IRCode code,
-      ProgramMethod context,
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
@@ -126,153 +108,18 @@
     if (field.isProgramField()) {
       return appView.appInfo().mayPropagateValueFor(appView, field.getReference());
     }
-    return appView.appInfo().assumedValues.containsKey(field.getReference())
-        || appView.appInfo().noSideEffects.containsKey(field.getReference());
+    return appView.getAssumeInfoCollection().contains(field);
   }
 
   private boolean mayPropagateValueFor(DexClassAndMethod method) {
     if (method.isProgramMethod()) {
       return appView.appInfo().mayPropagateValueFor(appView, method.getReference());
     }
-    return appView.appInfo().assumedValues.containsKey(method.getReference())
-        || appView.appInfo().noSideEffects.containsKey(method.getReference());
+    return appView.getAssumeInfoCollection().contains(method);
   }
 
-  private Instruction createReplacementFromAssumeInfo(
-      AssumeInfo assumeInfo, IRCode code, Instruction instruction) {
-    if (!assumeInfo.hasReturnInfo()) {
-      return null;
-    }
-
-    ProguardMemberRuleReturnValue returnValueRule = assumeInfo.getReturnInfo();
-
-    // Check if this value can be assumed constant.
-    if (returnValueRule.isSingleValue()) {
-      if (instruction.getOutType().isReferenceType()) {
-        if (returnValueRule.getSingleValue() == 0) {
-          return appView
-              .abstractValueFactory()
-              .createNullValue()
-              .createMaterializingInstruction(appView, code, instruction);
-        }
-        return null;
-      }
-      return appView.abstractValueFactory()
-          .createSingleNumberValue(returnValueRule.getSingleValue())
-          .createMaterializingInstruction(appView, code, instruction);
-    }
-
-    if (returnValueRule.isField()) {
-      DexField field = returnValueRule.getField();
-      assert instruction.getOutType() == TypeElement.fromDexType(field.type, maybeNull(), appView);
-
-      DexClassAndField staticField = appView.appInfo().lookupStaticTarget(field);
-      if (staticField == null) {
-        if (warnedFields.add(field)) {
-          reporter.warning(
-              new StringDiagnostic(
-                  "Field `"
-                      + field.toSourceString()
-                      + "` is used in an -assumevalues rule but does not exist.",
-                  code.origin));
-        }
-        return null;
-      }
-
-      if (AccessControl.isMemberAccessible(
-              staticField, staticField.getHolder(), code.context(), appView)
-          .isTrue()) {
-        return StaticGet.builder()
-            .setField(field)
-            .setFreshOutValue(code, field.getTypeElement(appView), instruction.getLocalInfo())
-            .build();
-      }
-
-      Instruction replacement =
-          staticField
-              .getDefinition()
-              .valueAsConstInstruction(code, instruction.getLocalInfo(), appView);
-      if (replacement == null) {
-        reporter.warning(
-            new StringDiagnostic(
-                "Unable to apply the rule `"
-                    + returnValueRule.toString()
-                    + "`: Could not determine the value of field `"
-                    + field.toSourceString()
-                    + "`",
-                code.origin));
-        return null;
-      }
-      return replacement;
-    }
-
-    return null;
-  }
-
-  private void setValueRangeFromAssumeInfo(AssumeInfo assumeInfo, Value value) {
-    if (assumeInfo.hasReturnInfo() && assumeInfo.getReturnInfo().isValueRange()) {
-      assert !assumeInfo.getReturnInfo().isSingleValue();
-      value.setValueRange(assumeInfo.getReturnInfo().getValueRange());
-    }
-  }
-
-  private boolean applyAssumeInfoIfPossible(
-      IRCode code,
-      Set<Value> affectedValues,
-      ListIterator<BasicBlock> blocks,
-      InstructionListIterator iterator,
-      Instruction current,
-      AssumeInfo assumeInfo) {
-    Instruction replacement = createReplacementFromAssumeInfo(assumeInfo, code, current);
-    if (replacement == null) {
-      // Check to see if a value range can be assumed.
-      if (current.getOutType().isPrimitiveType()) {
-        setValueRangeFromAssumeInfo(assumeInfo, current.outValue());
-      }
-      return false;
-    }
-    affectedValues.addAll(current.outValue().affectedValues());
-    if (assumeInfo.isAssumeNoSideEffects()) {
-      iterator.replaceCurrentInstruction(replacement);
-    } else {
-      assert assumeInfo.isAssumeValues();
-      BasicBlock block = current.getBlock();
-      Position position = current.getPosition();
-      if (current.hasOutValue()) {
-        assert replacement.outValue() != null;
-        current.outValue().replaceUsers(replacement.outValue());
-      }
-      if (current.isInstanceGet()) {
-        iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
-      } else if (current.isStaticGet()) {
-        StaticGet staticGet = current.asStaticGet();
-        iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
-            appView, code, staticGet.getField().holder);
-      }
-      replacement.setPosition(position);
-      if (block.hasCatchHandlers()) {
-        BasicBlock splitBlock = iterator.split(code, blocks);
-        splitBlock.listIterator(code).add(replacement);
-
-        // Process the materialized value.
-        blocks.previous();
-        assert !iterator.hasNext();
-        assert IteratorUtils.peekNext(blocks) == splitBlock;
-
-        return true;
-      } else {
-        iterator.add(replacement);
-      }
-    }
-
-    // Process the materialized value.
-    iterator.previous();
-    assert iterator.peekNext() == replacement;
-
-    return true;
-  }
-
-  private void rewriteInvokeMethodWithConstantValues(
+  @Override
+  void rewriteInvokeMethod(
       IRCode code,
       ProgramMethod context,
       Set<Value> affectedValues,
@@ -300,8 +147,7 @@
 
     DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
     AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget);
-    if (lookup != null
-        && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, invoke, lookup)) {
+    if (applyAssumeInfo(code, affectedValues, blocks, iterator, invoke, lookup)) {
       return;
     }
 
@@ -358,7 +204,27 @@
     }
   }
 
-  private void rewriteFieldGetWithConstantValues(
+  @Override
+  void rewriteInstanceGet(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      InstanceGet current) {
+    rewriteFieldGet(code, affectedValues, blocks, iterator, current);
+  }
+
+  @Override
+  void rewriteStaticGet(
+      IRCode code,
+      Set<Value> affectedValues,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator iterator,
+      StaticGet current) {
+    rewriteFieldGet(code, affectedValues, blocks, iterator, current);
+  }
+
+  private void rewriteFieldGet(
       IRCode code,
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
@@ -367,7 +233,7 @@
     DexField field = current.getField();
 
     // TODO(b/123857022): Should be able to use definitionFor().
-    SingleFieldResolutionResult resolutionResult =
+    SingleFieldResolutionResult<?> resolutionResult =
         appView.appInfo().resolveField(field).asSingleFieldResolutionResult();
     if (resolutionResult == null) {
       boolean replaceCurrentInstructionWithConstNull =
@@ -397,9 +263,8 @@
     }
 
     // Check if there is a Proguard configuration rule that specifies the value of the field.
-    AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, target);
-    if (lookup != null
-        && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, current, lookup)) {
+    AssumeInfo lookup = appView.getAssumeInfoCollection().get(target);
+    if (applyAssumeInfo(code, affectedValues, blocks, iterator, current, lookup)) {
       return;
     }
 
@@ -470,6 +335,11 @@
     }
   }
 
+  @Override
+  void rewriteInstancePut(IRCode code, InstructionListIterator iterator, InstancePut current) {
+    replaceInstancePutByNullCheckIfNeverRead(code, iterator, current);
+  }
+
   private void replaceInstancePutByNullCheckIfNeverRead(
       IRCode code, InstructionListIterator iterator, InstancePut current) {
     DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
@@ -487,6 +357,11 @@
     iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
   }
 
+  @Override
+  void rewriteStaticPut(IRCode code, InstructionListIterator iterator, StaticPut current) {
+    replaceStaticPutByInitClassIfNeverRead(code, iterator, current);
+  }
+
   private void replaceStaticPutByInitClassIfNeverRead(
       IRCode code, InstructionListIterator iterator, StaticPut current) {
     DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
@@ -504,55 +379,4 @@
     iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
         appView, code, field.getHolderType());
   }
-
-  /**
-   * Replace invoke targets and field accesses with constant values where possible.
-   *
-   * <p>Also assigns value ranges to values where possible.
-   */
-  public void run(IRCode code) {
-    IRMetadata metadata = code.metadata();
-    if (!metadata.mayHaveFieldInstruction() && !metadata.mayHaveInvokeMethod()) {
-      return;
-    }
-    Set<Value> affectedValues = Sets.newIdentityHashSet();
-    run(code, code.listIterator(), affectedValues, alwaysTrue());
-    if (!affectedValues.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(affectedValues);
-    }
-    assert code.isConsistentSSA(appView);
-    assert code.verifyTypes(appView);
-  }
-
-  public void run(
-      IRCode code,
-      ListIterator<BasicBlock> blockIterator,
-      Set<Value> affectedValues,
-      Predicate<BasicBlock> blockTester) {
-    ProgramMethod context = code.context();
-    while (blockIterator.hasNext()) {
-      BasicBlock block = blockIterator.next();
-      if (!blockTester.test(block)) {
-        continue;
-      }
-      InstructionListIterator iterator = block.listIterator(code);
-      while (iterator.hasNext()) {
-        Instruction current = iterator.next();
-        if (current.isArrayGet()) {
-          rewriteArrayGet(
-              code, context, affectedValues, blockIterator, iterator, current.asArrayGet());
-        } else if (current.isInvokeMethod()) {
-          rewriteInvokeMethodWithConstantValues(
-              code, context, affectedValues, blockIterator, iterator, current.asInvokeMethod());
-        } else if (current.isFieldGet()) {
-          rewriteFieldGetWithConstantValues(
-              code, affectedValues, blockIterator, iterator, current.asFieldInstruction());
-        } else if (current.isInstancePut()) {
-          replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut());
-        } else if (current.isStaticPut()) {
-          replaceStaticPutByInitClassIfNeverRead(code, iterator, current.asStaticPut());
-        }
-      }
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
index a704abb..21d8d33 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
@@ -4,64 +4,188 @@
 
 package com.android.tools.r8.ir.optimize.membervaluepropagation.assume;
 
-import com.android.tools.r8.shaking.ProguardMemberRule;
-import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import java.util.Objects;
 
 public class AssumeInfo {
 
-  public enum AssumeType {
-    ASSUME_NO_SIDE_EFFECTS,
-    ASSUME_VALUES;
+  private static final AssumeInfo EMPTY =
+      new AssumeInfo(DynamicType.unknown(), AbstractValue.unknown(), false);
 
-    AssumeType meet(AssumeType type) {
-      return this == ASSUME_NO_SIDE_EFFECTS || type == ASSUME_NO_SIDE_EFFECTS
-          ? ASSUME_NO_SIDE_EFFECTS
-          : ASSUME_VALUES;
+  private final DynamicType assumeType;
+  private final AbstractValue assumeValue;
+  private final boolean isSideEffectFree;
+
+  private AssumeInfo(DynamicType assumeType, AbstractValue assumeValue, boolean isSideEffectFree) {
+    this.assumeType = assumeType;
+    this.assumeValue = assumeValue;
+    this.isSideEffectFree = isSideEffectFree;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static AssumeInfo create(
+      DynamicType assumeType, AbstractValue assumeValue, boolean isSideEffectFree) {
+    return assumeType.isUnknown() && assumeValue.isUnknown() && !isSideEffectFree
+        ? empty()
+        : new AssumeInfo(assumeType, assumeValue, isSideEffectFree);
+  }
+
+  public static AssumeInfo empty() {
+    return EMPTY;
+  }
+
+  public DynamicType getAssumeType() {
+    return assumeType;
+  }
+
+  public AbstractValue getAssumeValue() {
+    return assumeValue;
+  }
+
+  public boolean isEmpty() {
+    if (this == empty()) {
+      return true;
     }
+    assert !assumeType.isUnknown() || !assumeValue.isUnknown() || isSideEffectFree;
+    return false;
   }
 
-  private final AssumeType type;
-  private final ProguardMemberRule rule;
-
-  public AssumeInfo(AssumeType type, ProguardMemberRule rule) {
-    this.type = type;
-    this.rule = rule;
+  public boolean isSideEffectFree() {
+    return isSideEffectFree;
   }
 
-  public boolean hasReturnInfo() {
-    return rule.hasReturnValue();
+  public AssumeInfo meet(AssumeInfo other) {
+    DynamicType meetType = internalMeetType(assumeType, other.assumeType);
+    AbstractValue meetValue = internalMeetValue(assumeValue, other.assumeValue);
+    boolean meetIsSideEffectFree =
+        internalMeetIsSideEffectFree(isSideEffectFree, other.isSideEffectFree);
+    return AssumeInfo.create(meetType, meetValue, meetIsSideEffectFree);
   }
 
-  public ProguardMemberRuleReturnValue getReturnInfo() {
-    return rule.getReturnValue();
+  private static DynamicType internalMeetType(DynamicType type, DynamicType other) {
+    if (type.equals(other)) {
+      return type;
+    }
+    if (type.isUnknown()) {
+      return other;
+    }
+    if (other.isUnknown()) {
+      return type;
+    }
+    return DynamicType.unknown();
   }
 
-  public boolean isAssumeNoSideEffects() {
-    return type == AssumeType.ASSUME_NO_SIDE_EFFECTS;
+  private static AbstractValue internalMeetValue(AbstractValue value, AbstractValue other) {
+    if (value.equals(other)) {
+      return value;
+    }
+    if (value.isUnknown()) {
+      return other;
+    }
+    if (other.isUnknown()) {
+      return value;
+    }
+    return AbstractValue.unknown();
   }
 
-  public boolean isAssumeValues() {
-    return type == AssumeType.ASSUME_VALUES;
+  private static boolean internalMeetIsSideEffectFree(
+      boolean isSideEffectFree, boolean otherIsSideEffectFree) {
+    return isSideEffectFree || otherIsSideEffectFree;
   }
 
-  public AssumeInfo meet(AssumeInfo lookup) {
-    return new AssumeInfo(type.meet(lookup.type), rule.hasReturnValue() ? rule : lookup.rule);
+  public AssumeInfo rewrittenWithLens(AppView<?> appView, GraphLens graphLens) {
+    // Verify that there is no need to rewrite the assumed type.
+    assert assumeType.isNotNullType() || assumeType.isUnknown();
+    // If the assumed value is a static field, then rewrite it.
+    if (assumeValue.isSingleFieldValue()) {
+      DexField field = assumeValue.asSingleFieldValue().getField();
+      DexField rewrittenField = graphLens.getRenamedFieldSignature(field);
+      if (rewrittenField != field) {
+        SingleFieldValue rewrittenAssumeValue =
+            appView
+                .abstractValueFactory()
+                .createSingleFieldValue(rewrittenField, ObjectState.empty());
+        return create(assumeType, rewrittenAssumeValue, isSideEffectFree);
+      }
+    }
+    return this;
+  }
+
+  public AssumeInfo withoutPrunedItems(PrunedItems prunedItems) {
+    // Verify that there is no need to prune the assumed type.
+    assert assumeType.isNotNullType() || assumeType.isUnknown();
+    // If the assumed value is a static field, and the static field is removed, then prune the
+    // assumed value.
+    if (assumeValue.isSingleFieldValue()
+        && prunedItems.isRemoved(assumeValue.asSingleFieldValue().getField())) {
+      return create(assumeType, AbstractValue.unknown(), isSideEffectFree);
+    }
+    return this;
   }
 
   @Override
   public boolean equals(Object other) {
-    if (other == null) {
-      return false;
+    if (this == other) {
+      return true;
     }
-    if (!(other instanceof AssumeInfo)) {
+    if (other == null || getClass() != other.getClass()) {
       return false;
     }
     AssumeInfo assumeInfo = (AssumeInfo) other;
-    return type == assumeInfo.type && rule == assumeInfo.rule;
+    return assumeValue.equals(assumeInfo.assumeValue)
+        && assumeType.equals(assumeInfo.assumeType)
+        && isSideEffectFree == assumeInfo.isSideEffectFree;
   }
 
   @Override
   public int hashCode() {
-    return type.ordinal() * 31 + rule.hashCode();
+    return Objects.hash(assumeValue, assumeType, isSideEffectFree);
+  }
+
+  public static class Builder {
+
+    private DynamicType assumeType = DynamicType.unknown();
+    private AbstractValue assumeValue = AbstractValue.unknown();
+    private boolean isSideEffectFree = false;
+
+    public Builder meet(AssumeInfo assumeInfo) {
+      return meetAssumeType(assumeInfo.assumeType)
+          .meetAssumeValue(assumeInfo.assumeValue)
+          .meetIsSideEffectFree(assumeInfo.isSideEffectFree);
+    }
+
+    public Builder meetAssumeType(DynamicType assumeType) {
+      this.assumeType = internalMeetType(this.assumeType, assumeType);
+      return this;
+    }
+
+    public Builder meetAssumeValue(AbstractValue assumeValue) {
+      this.assumeValue = internalMeetValue(this.assumeValue, assumeValue);
+      return this;
+    }
+
+    public Builder meetIsSideEffectFree(boolean isSideEffectFree) {
+      this.isSideEffectFree = internalMeetIsSideEffectFree(this.isSideEffectFree, isSideEffectFree);
+      return this;
+    }
+
+    public Builder setIsSideEffectFree() {
+      this.isSideEffectFree = true;
+      return this;
+    }
+
+    public AssumeInfo build() {
+      return create(assumeType, assumeValue, isSideEffectFree);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
index d207ac7..223fa00 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
@@ -4,15 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.membervaluepropagation.assume;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo.AssumeType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
 
 public class AssumeInfoLookup {
 
@@ -20,33 +16,12 @@
       AppView<AppInfoWithLiveness> appView,
       SingleResolutionResult<?> resolutionResult,
       DexClassAndMethod singleTarget) {
-    AssumeInfo resolutionLookup = lookupAssumeInfo(appView, resolutionResult.getResolutionPair());
-    if (resolutionLookup == null) {
-      return singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
-    }
+    AssumeInfoCollection assumeInfoCollection = appView.getAssumeInfoCollection();
+    AssumeInfo resolutionLookup = assumeInfoCollection.get(resolutionResult.getResolutionPair());
     AssumeInfo singleTargetLookup =
-        singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
+        singleTarget != null ? assumeInfoCollection.get(singleTarget) : null;
     return singleTargetLookup != null
         ? resolutionLookup.meet(singleTargetLookup)
         : resolutionLookup;
   }
-
-  public static AssumeInfo lookupAssumeInfo(
-      AppView<? extends AppInfoWithClassHierarchy> appView, DexClassAndMember<?, ?> member) {
-    DexMember<?, ?> reference = member.getReference();
-    ProguardMemberRule assumeNoSideEffectsRule = appView.rootSet().noSideEffects.get(reference);
-    ProguardMemberRule assumeValuesRule = appView.rootSet().assumedValues.get(reference);
-    if (assumeNoSideEffectsRule == null && assumeValuesRule == null) {
-      return null;
-    }
-    AssumeType type =
-        assumeNoSideEffectsRule != null
-            ? AssumeType.ASSUME_NO_SIDE_EFFECTS
-            : AssumeType.ASSUME_VALUES;
-    if ((assumeNoSideEffectsRule != null && assumeNoSideEffectsRule.hasReturnValue())
-        || assumeValuesRule == null) {
-      return new AssumeInfo(type, assumeNoSideEffectsRule);
-    }
-    return new AssumeInfo(type, assumeValuesRule);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 418a665..cdb9762 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -38,6 +38,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.NamingLens;
@@ -242,8 +243,15 @@
         clazz.accessFlags.setSuper();
       }
     }
+    boolean allowInvalidCfAccessFlags = false;
+    if (clazz
+        .getType()
+        .getDescriptor()
+        .endsWith(appView.dexItemFactory().createString("/package-info;"))) {
+      allowInvalidCfAccessFlags = true;
+    }
     int access =
-        options.testing.allowInvalidCfAccessFlags
+        allowInvalidCfAccessFlags || options.testing.allowInvalidCfAccessFlags
             ? clazz.accessFlags.materialize()
             : clazz.accessFlags.getAsCfAccessFlags();
     if (clazz.isDeprecated()) {
@@ -280,6 +288,10 @@
           : "A nest host cannot also be a nest member.";
     }
 
+    for (PermittedSubclassAttribute entry : clazz.getPermittedSubclassAttributes()) {
+      entry.write(writer, getNamingLens());
+    }
+
     if (clazz.isRecord()) {
       // TODO(b/169645628): Strip record components if not kept.
       for (DexEncodedField instanceField : clazz.instanceFields()) {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 0b0d20f..9f01beb 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -47,19 +47,20 @@
 
     private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
     private LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
+    private final Map<String, String> originalSourceFiles = new HashMap<>();
 
     @Override
     public ClassNamingForNameMapper.Builder classNamingBuilder(
         String renamedName, String originalName, Position position) {
       ClassNamingForNameMapper.Builder classNamingBuilder =
-          ClassNamingForNameMapper.builder(renamedName, originalName);
+          ClassNamingForNameMapper.builder(renamedName, originalName, originalSourceFiles::put);
       mapping.put(renamedName, classNamingBuilder);
       return classNamingBuilder;
     }
 
     @Override
     public ClassNameMapper build() {
-      return new ClassNameMapper(buildClassNameMappings(), mapVersions);
+      return new ClassNameMapper(buildClassNameMappings(), mapVersions, originalSourceFiles);
     }
 
     private ImmutableMap<String, ClassNamingForNameMapper> buildClassNameMappings() {
@@ -159,8 +160,7 @@
       LineReader reader,
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
-      boolean allowExperimentalMapping,
-      Set<String> buildForClass)
+      boolean allowExperimentalMapping)
       throws IOException {
     try (ProguardMapReader proguardReader =
         new ProguardMapReader(
@@ -178,12 +178,15 @@
   private BiMapContainer<String, String> nameMapping;
   private final Map<Signature, Signature> signatureMap = new HashMap<>();
   private final Set<MapVersionMappingInformation> mapVersions;
+  private final Map<String, String> originalSourceFiles;
 
   private ClassNameMapper(
       ImmutableMap<String, ClassNamingForNameMapper> classNameMappings,
-      Set<MapVersionMappingInformation> mapVersions) {
+      Set<MapVersionMappingInformation> mapVersions,
+      Map<String, String> originalSourceFiles) {
     this.classNameMappings = classNameMappings;
     this.mapVersions = mapVersions;
+    this.originalSourceFiles = originalSourceFiles;
   }
 
   public Map<String, ClassNamingForNameMapper> getClassNameMappings() {
@@ -235,6 +238,10 @@
     return descriptorToJavaType(asString, this);
   }
 
+  public String getSourceFile(String typeName) {
+    return originalSourceFiles.get(typeName);
+  }
+
   @Override
   public boolean hasMapping(DexType type) {
     String decoded = descriptorToJavaType(type.descriptor.toString());
@@ -259,7 +266,7 @@
     ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
     builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
     classNameMappings.forEach(builder::put);
-    return new ClassNameMapper(builder.build(), mapVersions);
+    return new ClassNameMapper(builder.build(), mapVersions, originalSourceFiles);
   }
 
   public boolean verifyIsSorted() {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 373f584..c1e5de7 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -23,6 +23,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -43,10 +44,15 @@
     private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap();
     private final Map<String, List<MemberNaming>> mappedFieldNamingsByName = Maps.newHashMap();
     private final List<MappingInformation> additionalMappingInfo = new ArrayList<>();
+    private final BiConsumer<String, String> originalSourceFileConsumer;
 
-    private Builder(String renamedName, String originalName) {
+    private Builder(
+        String renamedName,
+        String originalName,
+        BiConsumer<String, String> originalSourceFileConsumer) {
       this.originalName = originalName;
       this.renamedName = renamedName;
+      this.originalSourceFileConsumer = originalSourceFileConsumer;
     }
 
     @Override
@@ -108,6 +114,9 @@
         }
       }
       additionalMappingInfo.add(info);
+      if (info.isFileNameInformation()) {
+        originalSourceFileConsumer.accept(originalName, info.asFileNameInformation().getFileName());
+      }
     }
   }
 
@@ -205,8 +214,11 @@
     }
   }
 
-  static Builder builder(String renamedName, String originalName) {
-    return new Builder(renamedName, originalName);
+  static Builder builder(
+      String renamedName,
+      String originalName,
+      BiConsumer<String, String> originalSourceFileConsumer) {
+    return new Builder(renamedName, originalName, originalSourceFileConsumer);
   }
 
   public final String originalName;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 0f67814..2efdb3c 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -103,10 +103,11 @@
           currentResolutionResult.withInitialResolutionHolder(
               currentResolutionResult.getResolvedHolder()),
           contexts,
-          invokeType)) {
+          invokeType,
+          original)) {
         eligibleLibraryMethod = currentResolvedMethod.asLibraryMethod();
       }
-      if (appView.appInfo().isAssumeMethod(currentResolvedMethod)) {
+      if (appView.getAssumeInfoCollection().contains(currentResolvedMethod)) {
         break;
       }
       DexClass currentResolvedHolder = currentResolvedMethod.getHolder();
@@ -140,7 +141,8 @@
       DexClassAndMethod resolvedMethod,
       SingleResolutionResult<?> resolutionResult,
       ProgramMethodSet contexts,
-      Type invokeType) {
+      Type invokeType,
+      DexMethod original) {
     // TODO(b/194422791): It could potentially be that `original.holder` is not a subtype of
     //  `original.holder` on all API levels, in which case it is not OK to rebind to the resolved
     //  method.
@@ -149,7 +151,7 @@
         && !isInvokeSuperToInterfaceMethod(resolvedMethod, invokeType)
         && !isInvokeSuperToAbstractMethod(resolvedMethod, invokeType)
         && isApiSafeForMemberRebinding(
-            resolvedMethod.asLibraryMethod(), androidApiLevelCompute, options);
+            resolvedMethod.asLibraryMethod(), original, androidApiLevelCompute, options);
   }
 
   private boolean isAccessibleInAllContexts(
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
index 5c9e671..b381e18 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
@@ -73,6 +73,11 @@
   }
 
   @Override
+  public ErroneousCfFrameState popArray(AppView<?> appView) {
+    return pop();
+  }
+
+  @Override
   public ErroneousCfFrameState popInitialized(
       AppView<?> appView,
       DexType expectedType,
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
index 2131f02..dfa38f5 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
@@ -44,22 +44,21 @@
 
   public static ErroneousCfFrameState errorUnexpectedStack(
       FrameType frameType, DexType expectedType) {
-    return internalErrorUnexpectedStack(formatActual(frameType), formatExpected(expectedType));
+    return errorUnexpectedStack(frameType, formatExpected(expectedType));
   }
 
   public static ErroneousCfFrameState errorUnexpectedStack(
       FrameType frameType, FrameType expectedType) {
-    return internalErrorUnexpectedStack(formatActual(frameType), formatExpected(expectedType));
+    return errorUnexpectedStack(frameType, formatExpected(expectedType));
   }
 
   public static ErroneousCfFrameState errorUnexpectedStack(
       FrameType frameType, ValueType expectedType) {
-    return internalErrorUnexpectedStack(formatActual(frameType), formatExpected(expectedType));
+    return errorUnexpectedStack(frameType, formatExpected(expectedType));
   }
 
-  private static ErroneousCfFrameState internalErrorUnexpectedStack(
-      String actual, String expected) {
-    return internalError(actual, expected, "on stack");
+  public static ErroneousCfFrameState errorUnexpectedStack(FrameType frameType, String expected) {
+    return internalError(formatActual(frameType), expected, "on stack");
   }
 
   private static ErroneousCfFrameState internalError(
@@ -119,6 +118,8 @@
   public abstract CfFrameState popAndInitialize(
       AppView<?> appView, DexMethod constructor, CfAnalysisConfig config);
 
+  public abstract CfFrameState popArray(AppView<?> appView);
+
   public final CfFrameState popInitialized(AppView<?> appView, DexType expectedType) {
     return popInitialized(appView, expectedType, FunctionUtils::getFirst);
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
index e920d8e..f05ab67 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
@@ -192,6 +192,21 @@
   }
 
   @Override
+  public CfFrameState popArray(AppView<?> appView) {
+    return pop(
+        (state, head) ->
+            isArrayTypeOrNull(head) ? state : errorUnexpectedStack(head, "an array type"));
+  }
+
+  private static boolean isArrayTypeOrNull(FrameType frameType) {
+    if (frameType.isInitializedReferenceType()
+        && frameType.asInitializedReferenceType().getInitializedType().isArrayType()) {
+      return true;
+    }
+    return frameType.isNullType();
+  }
+
+  @Override
   public CfFrameState popInitialized(
       AppView<?> appView,
       DexType expectedType,
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
index 5f8569b..83cd59d 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
@@ -156,6 +156,11 @@
   }
 
   @Override
+  public CfFrameState popArray(AppView<?> appView) {
+    return this;
+  }
+
+  @Override
   public CfFrameState popInitialized(
       AppView<?> appView,
       DexType expectedType,
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
index 1ff65a4..13187cc 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
@@ -12,5 +12,7 @@
 
   abstract ClassNamingForNameMapper getClassNaming(String typeName);
 
+  abstract String getSourceFileForClass(String typeName);
+
   abstract Set<MapVersionMappingInformation> getMapVersions();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
index a5c1dd6..ebba380 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
@@ -73,7 +73,7 @@
                   proguardMapProducer.get(), buildForClass);
       return new ProguardMappingProviderImpl(
           ClassNameMapper.mapperFromLineReaderWithFiltering(
-              reader, diagnosticsHandler, true, allowExperimental, buildForClass));
+              reader, diagnosticsHandler, true, allowExperimental));
     } catch (Exception e) {
       throw new InvalidMappingFileException(e);
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
index dcfe2cb..c15efef 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
@@ -43,4 +43,9 @@
     }
     return classNameMapper.getClassNaming(typeName);
   }
+
+  @Override
+  String getSourceFileForClass(String typeName) {
+    return classNameMapper.getSourceFile(typeName);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
index 7f71ad6..80931c3 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -201,16 +201,7 @@
 
     @Override
     public RetracedSourceFile getSourceFile() {
-      String sourceFile = null;
-      if (mapper != null) {
-        for (MappingInformation info : mapper.getAdditionalMappingInfo()) {
-          if (info.isFileNameInformation()) {
-            sourceFile = info.asFileNameInformation().getFileName();
-            break;
-          }
-        }
-      }
-      return new RetracedSourceFileImpl(getRetracedClass().getClassReference(), sourceFile);
+      return RetraceUtils.getSourceFile(classReference, classResult.retracer);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index 0aa210c..8243d4a 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -221,7 +221,7 @@
 
   @Override
   public boolean isEmpty() {
-    return !mappedRanges.isEmpty();
+    return mappedRanges.isEmpty();
   }
 
   public static class ElementImpl implements RetraceFrameElement {
@@ -348,8 +348,7 @@
 
     @Override
     public RetracedSourceFile getSourceFile(RetracedClassMemberReference frame) {
-      return RetraceUtils.getSourceFileOrLookup(
-          frame.getHolderClass(), classElement, retraceFrameResult.retracer);
+      return RetraceUtils.getSourceFile(frame.getHolderClass(), retraceFrameResult.retracer);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
index e5a2579..af8526c 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
@@ -197,8 +197,8 @@
 
     @Override
     public RetracedSourceFile getSourceFile() {
-      return RetraceUtils.getSourceFileOrLookup(
-          methodReference.getHolderClass(), classElement, retraceMethodResult.retracer);
+      return RetraceUtils.getSourceFile(
+          methodReference.getHolderClass(), retraceMethodResult.retracer);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index c4ed860..2e1411a 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -12,14 +12,10 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.RetraceClassElement;
-import com.android.tools.r8.retrace.RetraceClassResult;
 import com.android.tools.r8.retrace.RetracedClassReference;
 import com.android.tools.r8.retrace.RetracedMethodReference;
 import com.android.tools.r8.retrace.RetracedMethodReference.KnownRetracedMethodReference;
 import com.android.tools.r8.retrace.RetracedSourceFile;
-import com.android.tools.r8.retrace.Retracer;
-import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.Sets;
 import com.google.common.io.Files;
@@ -75,16 +71,10 @@
     return clazz.substring(lastIndexOfPeriod + 1, endIndex);
   }
 
-  // TODO(b/226885646): Retracing of a source file should not be dependent on retraced information.
-  public static RetracedSourceFile getSourceFileOrLookup(
-      RetracedClassReference holder, RetraceClassElement context, Retracer retracer) {
-    if (holder.equals(context.getRetracedClass())) {
-      return context.getSourceFile();
-    }
-    RetraceClassResult contextClassResult = retracer.retraceClass(holder.getClassReference());
-    Box<RetracedSourceFile> retraceSourceFile = new Box<>();
-    contextClassResult.forEach(element -> retraceSourceFile.set(element.getSourceFile()));
-    return retraceSourceFile.get();
+  public static RetracedSourceFile getSourceFile(
+      RetracedClassReference holder, RetracerImpl retracer) {
+    ClassReference holderReference = holder.getClassReference();
+    return new RetracedSourceFileImpl(holderReference, retracer.getSourceFile(holderReference));
   }
 
   public static String inferSourceFile(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index 92e42f1..346b417 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -87,6 +87,10 @@
     return classNameMapperProvider.getMapVersions();
   }
 
+  public String getSourceFile(ClassReference classReference) {
+    return classNameMapperProvider.getSourceFileForClass(classReference.getTypeName());
+  }
+
   public static Builder builder() {
     return new Builder();
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
index 987aead..a8b5e71 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -131,7 +131,7 @@
                                   ? OptionalInt.of(element.getLineNumber())
                                   : OptionalInt.empty(),
                               element.getMethodName());
-                      if (!frameResult.isEmpty()) {
+                      if (frameResult.isEmpty()) {
                         return classResult.stream()
                             .map(
                                 classElement ->
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 94b44d0..677338b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMember;
-import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
@@ -143,10 +142,6 @@
   private final KeepInfoCollection keepInfo;
   /** All items with assumemayhavesideeffects rule. */
   public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
-  /** All items with assumenosideeffects rule. */
-  public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
-  /** All items with assumevalues rule. */
-  public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
   /** All methods that should be inlined if possible due to a configuration directive. */
   private final Set<DexMethod> alwaysInline;
   /**
@@ -222,8 +217,6 @@
       Map<DexCallSite, ProgramMethodSet> callSites,
       KeepInfoCollection keepInfo,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
-      Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
-      Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
       Set<DexMethod> alwaysInline,
       Set<DexMethod> neverInlineDueToSingleCaller,
       Set<DexMethod> whyAreYouNotInlining,
@@ -255,8 +248,6 @@
     this.objectAllocationInfoCollection = objectAllocationInfoCollection;
     this.keepInfo = keepInfo;
     this.mayHaveSideEffects = mayHaveSideEffects;
-    this.noSideEffects = noSideEffects;
-    this.assumedValues = assumedValues;
     this.callSites = callSites;
     this.alwaysInline = alwaysInline;
     this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
@@ -299,8 +290,6 @@
         previous.callSites,
         previous.keepInfo,
         previous.mayHaveSideEffects,
-        previous.noSideEffects,
-        previous.assumedValues,
         previous.alwaysInline,
         previous.neverInlineDueToSingleCaller,
         previous.whyAreYouNotInlining,
@@ -346,8 +335,6 @@
         previous.callSites,
         extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()),
         previous.mayHaveSideEffects,
-        pruneMapFromMembers(previous.noSideEffects, prunedItems, executorService, futures),
-        pruneMapFromMembers(previous.assumedValues, prunedItems, executorService, futures),
         pruneMethods(previous.alwaysInline, prunedItems, executorService, futures),
         pruneMethods(previous.neverInlineDueToSingleCaller, prunedItems, executorService, futures),
         pruneMethods(previous.whyAreYouNotInlining, prunedItems, executorService, futures),
@@ -551,8 +538,6 @@
         callSites,
         keepInfo,
         mayHaveSideEffects,
-        noSideEffects,
-        assumedValues,
         alwaysInline,
         neverInlineDueToSingleCaller,
         whyAreYouNotInlining,
@@ -630,8 +615,6 @@
     this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
     this.keepInfo = previous.keepInfo;
     this.mayHaveSideEffects = previous.mayHaveSideEffects;
-    this.noSideEffects = previous.noSideEffects;
-    this.assumedValues = previous.assumedValues;
     this.callSites = previous.callSites;
     this.alwaysInline = previous.alwaysInline;
     this.neverInlineDueToSingleCaller = previous.neverInlineDueToSingleCaller;
@@ -762,26 +745,6 @@
     return neverInlineDueToSingleCaller.contains(method.getReference());
   }
 
-  public boolean isAssumeMethod(DexClassAndMethod method) {
-    return isAssumeNoSideEffectsMethod(method) || isAssumeValuesMethod(method);
-  }
-
-  public boolean isAssumeNoSideEffectsMethod(DexMethod method) {
-    return noSideEffects.containsKey(method);
-  }
-
-  public boolean isAssumeNoSideEffectsMethod(DexClassAndMethod method) {
-    return isAssumeNoSideEffectsMethod(method.getReference());
-  }
-
-  public boolean isAssumeValuesMethod(DexMethod method) {
-    return assumedValues.containsKey(method);
-  }
-
-  public boolean isAssumeValuesMethod(DexClassAndMethod method) {
-    return isAssumeValuesMethod(method.getReference());
-  }
-
   public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
     return whyAreYouNotInlining.contains(method);
   }
@@ -950,9 +913,8 @@
       assert info.isRead() || info.isWritten();
       return true;
     }
-    // TODO(b/192924387): When we enqueue a field as a root item, we should maybe create a
-    //  FieldAccessInfo that describes the field is read and written using reflection.
-    return !getKeepInfo().getFieldInfo(reference, this).isShrinkingAllowed(options());
+    assert getKeepInfo().getFieldInfo(reference, this).isShrinkingAllowed(options());
+    return false;
   }
 
   public boolean isFieldRead(DexEncodedField encodedField) {
@@ -1240,13 +1202,6 @@
         keepInfo.rewrite(definitionSupplier, lens, application.options),
         // Take any rule in case of collisions.
         lens.rewriteReferenceKeys(mayHaveSideEffects, (reference, rules) -> ListUtils.first(rules)),
-        // Take the assume rule from the representative in case of collisions.
-        lens.rewriteReferenceKeys(
-            noSideEffects,
-            (reference, rules) -> noSideEffects.get(lens.getOriginalMemberSignature(reference))),
-        lens.rewriteReferenceKeys(
-            assumedValues,
-            (reference, rules) -> assumedValues.get(lens.getOriginalMemberSignature(reference))),
         lens.rewriteReferences(alwaysInline),
         lens.rewriteReferences(neverInlineDueToSingleCaller),
         lens.rewriteReferences(whyAreYouNotInlining),
diff --git a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java
new file mode 100644
index 0000000..efff258
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2022, 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;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.utils.MapUtils;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+public class AssumeInfoCollection {
+
+  private final Map<DexMember<?, ?>, AssumeInfo> backing;
+
+  AssumeInfoCollection(Map<DexMember<?, ?>, AssumeInfo> backing) {
+    assert backing.values().stream().noneMatch(AssumeInfo::isEmpty);
+    this.backing = backing;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public boolean contains(DexClassAndMember<?, ?> member) {
+    return backing.containsKey(member.getReference());
+  }
+
+  public AssumeInfo get(DexMember<?, ?> member) {
+    return backing.getOrDefault(member, AssumeInfo.empty());
+  }
+
+  public AssumeInfo get(DexClassAndMember<?, ?> member) {
+    return get(member.getReference());
+  }
+
+  public boolean isSideEffectFree(DexMember<?, ?> member) {
+    return get(member).isSideEffectFree();
+  }
+
+  public boolean isSideEffectFree(DexClassAndMember<?, ?> member) {
+    return isSideEffectFree(member.getReference());
+  }
+
+  public AssumeInfoCollection rewrittenWithLens(AppView<?> appView, GraphLens graphLens) {
+    Map<DexMember<?, ?>, AssumeInfo> rewrittenCollection = new IdentityHashMap<>();
+    backing.forEach(
+        (reference, info) -> {
+          DexMember<?, ?> rewrittenReference = graphLens.getRenamedMemberSignature(reference);
+          AssumeInfo rewrittenInfo = info.rewrittenWithLens(appView, graphLens);
+          assert !rewrittenInfo.isEmpty();
+          rewrittenCollection.put(rewrittenReference, rewrittenInfo);
+        });
+    return new AssumeInfoCollection(rewrittenCollection);
+  }
+
+  public AssumeInfoCollection withoutPrunedItems(PrunedItems prunedItems) {
+    Map<DexMember<?, ?>, AssumeInfo> rewrittenCollection = new IdentityHashMap<>();
+    backing.forEach(
+        (reference, info) -> {
+          if (!prunedItems.isRemoved(reference)) {
+            AssumeInfo rewrittenInfo = info.withoutPrunedItems(prunedItems);
+            if (!rewrittenInfo.isEmpty()) {
+              rewrittenCollection.put(reference, rewrittenInfo);
+            }
+          }
+        });
+    return new AssumeInfoCollection(rewrittenCollection);
+  }
+
+  public static class Builder {
+
+    private final Map<DexMember<?, ?>, AssumeInfo.Builder> backing = new ConcurrentHashMap<>();
+
+    public Builder applyIf(boolean condition, Consumer<Builder> consumer) {
+      if (condition) {
+        consumer.accept(this);
+      }
+      return this;
+    }
+
+    public AssumeInfo buildInfo(DexClassAndMember<?, ?> member) {
+      AssumeInfo.Builder builder = backing.get(member.getReference());
+      return builder != null ? builder.build() : AssumeInfo.empty();
+    }
+
+    private AssumeInfo.Builder getOrCreateAssumeInfo(DexMember<?, ?> member) {
+      return backing.computeIfAbsent(member, ignoreKey(AssumeInfo::builder));
+    }
+
+    private AssumeInfo.Builder getOrCreateAssumeInfo(DexClassAndMember<?, ?> member) {
+      return getOrCreateAssumeInfo(member.getReference());
+    }
+
+    public boolean isEmpty() {
+      return backing.isEmpty();
+    }
+
+    public Builder meet(DexMember<?, ?> member, AssumeInfo assumeInfo) {
+      getOrCreateAssumeInfo(member).meet(assumeInfo);
+      return this;
+    }
+
+    public Builder meetAssumeType(DexClassAndMember<?, ?> member, DynamicType assumeType) {
+      getOrCreateAssumeInfo(member).meetAssumeType(assumeType);
+      return this;
+    }
+
+    public Builder meetAssumeValue(DexMember<?, ?> member, AbstractValue assumeValue) {
+      getOrCreateAssumeInfo(member).meetAssumeValue(assumeValue);
+      return this;
+    }
+
+    public Builder meetAssumeValue(DexClassAndMember<?, ?> member, AbstractValue assumeValue) {
+      return meetAssumeValue(member.getReference(), assumeValue);
+    }
+
+    public Builder setIsSideEffectFree(DexMember<?, ?> member) {
+      getOrCreateAssumeInfo(member).setIsSideEffectFree();
+      return this;
+    }
+
+    public Builder setIsSideEffectFree(DexClassAndMember<?, ?> member) {
+      return setIsSideEffectFree(member.getReference());
+    }
+
+    public AssumeInfoCollection build() {
+      return new AssumeInfoCollection(
+          MapUtils.newIdentityHashMap(
+              builder ->
+                  backing.forEach(
+                      (reference, infoBuilder) -> {
+                        AssumeInfo info = infoBuilder.build();
+                        if (!info.isEmpty()) {
+                          builder.accept(reference, info);
+                        }
+                      }),
+              backing.size()));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/CheckEnumUnboxedRule.java b/src/main/java/com/android/tools/r8/shaking/CheckEnumUnboxedRule.java
new file mode 100644
index 0000000..da2fbbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/CheckEnumUnboxedRule.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2022, 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class CheckEnumUnboxedRule extends ProguardConfigurationRule {
+
+  public static final String RULE_NAME = "checkenumunboxed";
+
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<CheckEnumUnboxedRule, Builder> {
+
+    private Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public CheckEnumUnboxedRule build() {
+      return new CheckEnumUnboxedRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
+    }
+  }
+
+  protected CheckEnumUnboxedRule(
+      Origin origin,
+      Position position,
+      String source,
+      List<ProguardTypeMatcher> classAnnotations,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return RULE_NAME;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ConvertCheckNotNullRule.java b/src/main/java/com/android/tools/r8/shaking/ConvertCheckNotNullRule.java
new file mode 100644
index 0000000..dd372e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ConvertCheckNotNullRule.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2022, 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ConvertCheckNotNullRule extends ProguardConfigurationRule {
+
+  public static final String RULE_NAME = "convertchecknotnull";
+
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ConvertCheckNotNullRule, Builder> {
+
+    private Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public ConvertCheckNotNullRule build() {
+      return new ConvertCheckNotNullRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
+    }
+  }
+
+  private ConvertCheckNotNullRule(
+      Origin origin,
+      Position position,
+      String source,
+      List<ProguardTypeMatcher> classAnnotations,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+  }
+
+  /** Create a new empty builder. */
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public boolean applyToNonProgramClasses() {
+    return true;
+  }
+
+  @Override
+  String typeString() {
+    return RULE_NAME;
+  }
+}
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 f7d3e74..3addb2d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
@@ -1045,6 +1046,19 @@
     }
   }
 
+  private FieldAccessInfoImpl getOrCreateFieldAccessInfo(DexEncodedField field) {
+    // Check if we have previously created a FieldAccessInfo object for the field definition.
+    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.getReference());
+
+    // If not, we must create one.
+    if (info == null) {
+      info = new FieldAccessInfoImpl(field.getReference());
+      fieldAccessInfoCollection.extend(field.getReference(), info);
+    }
+
+    return info;
+  }
+
   private boolean registerFieldAccess(
       DexField field, ProgramMethod context, boolean isRead, boolean isReflective) {
     FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
@@ -1058,14 +1072,7 @@
         return true;
       }
 
-      // Check if we have previously created a FieldAccessInfo object for the field definition.
-      info = fieldAccessInfoCollection.get(encodedField.getReference());
-
-      // If not, we must create one.
-      if (info == null) {
-        info = new FieldAccessInfoImpl(encodedField.getReference());
-        fieldAccessInfoCollection.extend(encodedField.getReference(), info);
-      }
+      info = getOrCreateFieldAccessInfo(encodedField);
 
       // If `field` is an indirect reference, then create a mapping for it, such that we don't have
       // to resolve the field the next time we see the reference.
@@ -2028,6 +2035,11 @@
 
     assert !appView.unboxedEnums().isUnboxedEnum(clazz);
 
+    if (options.isGeneratingClassFiles() && clazz.hasPermittedSubclassAttributes()) {
+      throw new CompilationError(
+          "Sealed classes are not supported as program classes when generating class files",
+          clazz.getOrigin());
+    }
     // Mark types in inner-class attributes referenced.
     {
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer =
@@ -4137,8 +4149,6 @@
             callSites,
             keepInfo,
             rootSet.mayHaveSideEffects,
-            rootSet.noSideEffects,
-            rootSet.assumedValues,
             amendWithCompanionMethods(rootSet.alwaysInline),
             amendWithCompanionMethods(rootSet.neverInlineDueToSingleCaller),
             amendWithCompanionMethods(rootSet.whyAreYouNotInlining),
@@ -4553,6 +4563,10 @@
 
   // Package protected due to entry point from worklist.
   void markFieldAsKept(ProgramField field, KeepReason reason) {
+    FieldAccessInfoImpl fieldAccessInfo = getOrCreateFieldAccessInfo(field.getDefinition());
+    fieldAccessInfo.setHasReflectiveRead();
+    fieldAccessInfo.setHasReflectiveWrite();
+
     if (field.getDefinition().isStatic()) {
       markFieldAsLive(field, field, reason);
     } else {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index 85d14b9..41c48cb 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.ir.conversion.IRToDexFinalizer;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
-import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
 import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
 import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata;
 import com.android.tools.r8.shaking.Enqueuer.Mode;
@@ -99,8 +98,8 @@
       // If the value of the field is not guaranteed to be the default value, even if it is never
       // assigned, then give up.
       // TODO(b/205810841): Allow this by handling this in the corresponding IR rewriter.
-      AssumeInfo assumeInfo = AssumeInfoLookup.lookupAssumeInfo(appView, field);
-      if (assumeInfo != null && assumeInfo.hasReturnInfo()) {
+      AssumeInfo assumeInfo = appView.getAssumeInfoCollection().get(field);
+      if (!assumeInfo.getAssumeValue().isUnknown()) {
         return enqueueDeferredEnqueuerActions(field);
       }
       if (field.getAccessFlags().isStatic() && field.getDefinition().hasExplicitStaticValue()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index a09efe6..b2f5f9c 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -736,7 +736,7 @@
     @Override
     public void enqueueTraceReflectiveFieldAccessAction(ProgramField field, ProgramMethod context) {
       FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
-      if (info == null || !info.hasReflectiveAccess()) {
+      if (info == null || !info.hasReflectiveRead() || !info.hasReflectiveWrite()) {
         queue.add(new TraceReflectiveFieldAccessAction(field, context));
       }
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index bf969b7..7199fcb 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -32,8 +32,11 @@
     return bottom().joiner();
   }
 
+  private final boolean checkEnumUnboxed;
+
   private KeepClassInfo(Builder builder) {
     super(builder);
+    this.checkEnumUnboxed = builder.isCheckEnumUnboxedEnabled();
   }
 
   @Override
@@ -41,6 +44,14 @@
     return new Builder(this);
   }
 
+  public boolean isCheckEnumUnboxedEnabled(GlobalKeepInfoConfiguration configuration) {
+    return internalIsCheckEnumUnboxedEnabled();
+  }
+
+  boolean internalIsCheckEnumUnboxedEnabled() {
+    return checkEnumUnboxed;
+  }
+
   public Joiner joiner() {
     assert !isTop();
     return new Joiner(this);
@@ -87,12 +98,34 @@
 
   public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> {
 
+    private boolean checkEnumUnboxed;
+
     private Builder() {
       super();
     }
 
     private Builder(KeepClassInfo original) {
       super(original);
+      checkEnumUnboxed = original.internalIsCheckEnumUnboxedEnabled();
+    }
+
+    // Check enum unboxed.
+
+    public boolean isCheckEnumUnboxedEnabled() {
+      return checkEnumUnboxed;
+    }
+
+    public Builder setCheckEnumUnboxed(boolean checkEnumUnboxed) {
+      this.checkEnumUnboxed = checkEnumUnboxed;
+      return self();
+    }
+
+    public Builder setCheckEnumUnboxed() {
+      return setCheckEnumUnboxed(true);
+    }
+
+    public Builder unsetCheckEnumUnboxed() {
+      return setCheckEnumUnboxed(false);
     }
 
     @Override
@@ -116,9 +149,25 @@
     }
 
     @Override
+    boolean internalIsEqualTo(KeepClassInfo other) {
+      return super.internalIsEqualTo(other)
+          && isCheckEnumUnboxedEnabled() == other.internalIsCheckEnumUnboxedEnabled();
+    }
+
+    @Override
     public KeepClassInfo doBuild() {
       return new KeepClassInfo(this);
     }
+
+    @Override
+    public Builder makeTop() {
+      return super.makeTop().unsetCheckEnumUnboxed();
+    }
+
+    @Override
+    public Builder makeBottom() {
+      return super.makeBottom().unsetCheckEnumUnboxed();
+    }
   }
 
   public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepClassInfo> {
@@ -127,6 +176,11 @@
       super(info.builder());
     }
 
+    public Joiner setCheckEnumUnboxed() {
+      builder.setCheckEnumUnboxed();
+      return self();
+    }
+
     @Override
     public Joiner asClassJoiner() {
       return this;
@@ -135,7 +189,8 @@
     @Override
     public Joiner merge(Joiner joiner) {
       // Should be extended to merge the fields of this class in case any are added.
-      return super.merge(joiner);
+      return super.merge(joiner)
+          .applyIf(joiner.builder.isCheckEnumUnboxedEnabled(), Joiner::setCheckEnumUnboxed);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java b/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
index 7f9ff8c..9bd37a5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
@@ -18,21 +18,22 @@
   private int flags = 0;
 
   // Ordered list of flag names. Must be consistent with getPredicates.
-  private static final List<String> NAMES = ImmutableList.of(
-      "public",
-      "private",
-      "protected",
-      "static",
-      "final",
-      "abstract",
-      "volatile",
-      "transient",
-      "synchronized",
-      "native",
-      "strictfp",
-      "synthetic",
-      "bridge"
-  );
+  private static final List<String> NAMES =
+      ImmutableList.of(
+          "public",
+          "private",
+          "protected",
+          "static",
+          "final",
+          "abstract",
+          "volatile",
+          "transient",
+          "synchronized",
+          "native",
+          "strictfp",
+          "synthetic",
+          "bridge",
+          "constructor");
 
   // Get ordered list of flag predicates. Must be consistent with getNames.
   private List<BooleanSupplier> getPredicates() {
@@ -49,7 +50,8 @@
         this::isNative,
         this::isStrict,
         this::isSynthetic,
-        this::isBridge);
+        this::isBridge,
+        this::isConstructor);
   }
 
   private boolean containsAll(int other) {
@@ -194,6 +196,14 @@
     return isSet(Constants.ACC_BRIDGE);
   }
 
+  public void setConstructor() {
+    set(Constants.ACC_CONSTRUCTOR);
+  }
+
+  public boolean isConstructor() {
+    return isSet(Constants.ACC_CONSTRUCTOR);
+  }
+
   private boolean isSet(int flag) {
     return (flags & flag) != 0;
   }
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 c1f5a95..36bd397 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -70,7 +70,7 @@
     private boolean configurationDebugging = false;
     private boolean dontUseMixedCaseClassnames = false;
     private boolean protoShrinking = false;
-    private int maxRemovedAndroidLogLevel = 1;
+    private int maxRemovedAndroidLogLevel = -1;
 
     private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
       this.dexItemFactory = dexItemFactory;
@@ -294,12 +294,21 @@
       protoShrinking = true;
     }
 
-    public int getMaxRemovedAndroidLogLevel() {
-      return maxRemovedAndroidLogLevel;
+    public int getMaxRemovedAndroidLogLevelOrDefault(int defaultValue) {
+      assert maxRemovedAndroidLogLevel == -1 || maxRemovedAndroidLogLevel >= 1;
+      return maxRemovedAndroidLogLevel >= 1 ? maxRemovedAndroidLogLevel : defaultValue;
     }
 
-    public void setMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
-      this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+    public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
+      assert maxRemovedAndroidLogLevel >= 1;
+      if (this.maxRemovedAndroidLogLevel == -1) {
+        this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+      } else {
+        // If there are multiple -maximumremovedandroidloglevel rules we only allow removing logging
+        // calls that are removable according to all rules.
+        this.maxRemovedAndroidLogLevel =
+            Math.min(this.maxRemovedAndroidLogLevel, maxRemovedAndroidLogLevel);
+      }
     }
 
     public ProguardConfiguration buildRaw() {
@@ -344,7 +353,7 @@
               configurationDebugging,
               dontUseMixedCaseClassnames,
               protoShrinking,
-              maxRemovedAndroidLogLevel);
+              getMaxRemovedAndroidLogLevelOrDefault(1));
 
       reporter.failIfPendingErrors();
 
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 c4af05f..8b4210b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -8,10 +8,10 @@
 import com.android.tools.r8.InputDependencyGraphConsumer;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
@@ -19,7 +19,6 @@
 import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
-import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
 import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
 import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
 import com.android.tools.r8.utils.IdentifierUtils;
@@ -47,12 +46,10 @@
 public class ProguardConfigurationParser {
 
   private final Builder configurationBuilder;
-
   private final DexItemFactory dexItemFactory;
-
+  private final ProguardConfigurationParserOptions options;
   private final Reporter reporter;
   private final InputDependencyGraphConsumer inputDependencyConsumer;
-  private final boolean allowTestOptions;
 
   public static final String FLATTEN_PACKAGE_HIERARCHY = "flattenpackagehierarchy";
   public static final String REPACKAGE_CLASSES = "repackageclasses";
@@ -65,26 +62,25 @@
   private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS =
       ImmutableList.of("runtype", "laststageoutput");
 
-  private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
-      "forceprocessing",
-      "dontpreverify",
-      "experimentalshrinkunusedprotofields",
-      "filterlibraryjarswithorginalprogramjars",
-      "dontskipnonpubliclibraryclasses",
-      "dontskipnonpubliclibraryclassmembers",
-      "invokebasemethod",
-      // TODO(b/62524562): we may support this later.
-      "mergeinterfacesaggressively",
-      "android",
-      "allowruntypeandignoreoptimizationpasses",
-      "dontshrinkduringoptimization",
-      "convert_proto_enum_to_string");
+  private static final List<String> IGNORED_FLAG_OPTIONS =
+      ImmutableList.of(
+          "forceprocessing",
+          "dontpreverify",
+          "experimentalshrinkunusedprotofields",
+          "filterlibraryjarswithorginalprogramjars",
+          "dontskipnonpubliclibraryclasses",
+          "dontskipnonpubliclibraryclassmembers",
+          "invokebasemethod",
+          // TODO(b/62524562): we may support this later.
+          "mergeinterfacesaggressively",
+          "android",
+          "allowruntypeandignoreoptimizationpasses",
+          "dontshrinkduringoptimization",
+          "convert_proto_enum_to_string",
+          "keepkotlinmetadata");
 
   private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS =
-      ImmutableList.of(
-          "isclassnamestring", "whyarenotsimple", "convertchecknotnull", "checkenumunboxed");
-
-  private static final List<String> IGNORED_RETURN_VALUE_ATTRIBUTES = ImmutableList.of("_NONNULL_");
+      ImmutableList.of("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!
@@ -122,23 +118,37 @@
 
   public ProguardConfigurationParser(
       DexItemFactory dexItemFactory, Reporter reporter) {
-    this(dexItemFactory, reporter, null, false);
+    this(
+        dexItemFactory,
+        reporter,
+        ProguardConfigurationParserOptions.builder()
+            .setEnableExperimentalCheckEnumUnboxed(false)
+            .setEnableExperimentalConvertCheckNotNull(false)
+            .setEnableExperimentalWhyAreYouNotInlining(false)
+            .setEnableTestingOptions(false)
+            .build());
   }
 
   public ProguardConfigurationParser(
       DexItemFactory dexItemFactory,
       Reporter reporter,
-      InputDependencyGraphConsumer inputDependencyConsumer,
-      boolean allowTestOptions) {
-    this.dexItemFactory = dexItemFactory;
-    configurationBuilder = ProguardConfiguration.builder(dexItemFactory, reporter);
+      ProguardConfigurationParserOptions options) {
+    this(dexItemFactory, reporter, options, null);
+  }
 
+  public ProguardConfigurationParser(
+      DexItemFactory dexItemFactory,
+      Reporter reporter,
+      ProguardConfigurationParserOptions options,
+      InputDependencyGraphConsumer inputDependencyConsumer) {
+    this.configurationBuilder = ProguardConfiguration.builder(dexItemFactory, reporter);
+    this.dexItemFactory = dexItemFactory;
+    this.options = options;
     this.reporter = reporter;
     this.inputDependencyConsumer =
         inputDependencyConsumer != null
             ? inputDependencyConsumer
             : emptyInputDependencyGraphConsumer();
-    this.allowTestOptions = allowTestOptions;
   }
 
   private static InputDependencyGraphConsumer emptyInputDependencyGraphConsumer() {
@@ -272,6 +282,7 @@
       expectChar('-');
       if (parseIgnoredOption(optionStart)
           || parseIgnoredOptionAndWarn(optionStart)
+          || parseExperimentalOption(optionStart)
           || parseTestingOption(optionStart)
           || parseUnsupportedOptionAndErr(optionStart)) {
         // Intentionally left empty.
@@ -454,10 +465,10 @@
       } else if (acceptString("maximumremovedandroidloglevel")) {
         skipWhitespace();
         Integer maxRemovedAndroidLogLevel = acceptInteger();
-        if (maxRemovedAndroidLogLevel != null) {
-          configurationBuilder.setMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
+        if (maxRemovedAndroidLogLevel != null && maxRemovedAndroidLogLevel >= 1) {
+          configurationBuilder.joinMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
         } else {
-          throw parseError("Expected integer", getPosition());
+          throw parseError("Expected integer greater than or equal to 1", getPosition());
         }
       } else {
         String unknownOption = acceptString();
@@ -472,9 +483,34 @@
       return true;
     }
 
+    private boolean parseExperimentalOption(TextPosition optionStart)
+        throws ProguardRuleParserException {
+      if (acceptString(CheckEnumUnboxedRule.RULE_NAME)) {
+        CheckEnumUnboxedRule checkEnumUnboxedRule = parseCheckEnumUnboxedRule(optionStart);
+        if (options.isExperimentalCheckEnumUnboxedEnabled()) {
+          configurationBuilder.addRule(checkEnumUnboxedRule);
+        }
+        return true;
+      }
+      if (acceptString(ConvertCheckNotNullRule.RULE_NAME)) {
+        ConvertCheckNotNullRule convertCheckNotNullRule = parseConvertCheckNotNullRule(optionStart);
+        if (options.isExperimentalConvertCheckNotNullEnabled()) {
+          configurationBuilder.addRule(convertCheckNotNullRule);
+        }
+        return true;
+      }
+      if (options.isExperimentalWhyAreYouNotInliningEnabled()) {
+        if (acceptString(WhyAreYouNotInliningRule.RULE_NAME)) {
+          configurationBuilder.addRule(parseWhyAreYouNotInliningRule(optionStart));
+          return true;
+        }
+      }
+      return false;
+    }
+
     private boolean parseTestingOption(TextPosition optionStart)
         throws ProguardRuleParserException {
-      if (allowTestOptions) {
+      if (options.isTestingOptionsEnabled()) {
         if (acceptString("assumemayhavesideeffects")) {
           ProguardAssumeMayHaveSideEffectsRule rule =
               parseAssumeMayHaveSideEffectsRule(optionStart);
@@ -591,11 +627,6 @@
               parseReprocessMethodRule(ReprocessMethodRule.Type.ALWAYS, optionStart));
           return true;
         }
-        if (acceptString("whyareyounotinlining")) {
-          WhyAreYouNotInliningRule rule = parseWhyAreYouNotInliningRule(optionStart);
-          configurationBuilder.addRule(rule);
-          return true;
-        }
       }
       return false;
     }
@@ -651,19 +682,6 @@
           || parseOptimizationOption(optionStart);
     }
 
-    private boolean parseIgnoredReturnValueAttribute() {
-      return Iterables.any(IGNORED_RETURN_VALUE_ATTRIBUTES, this::skipReturnValueAttribute);
-    }
-
-    private boolean parseIgnoredReturnValueAttributes() {
-      boolean anySkipped = false;
-      while (parseIgnoredReturnValueAttribute()) {
-        anySkipped = true;
-        skipWhitespace();
-      }
-      return anySkipped;
-    }
-
     private void parseInclude() throws ProguardRuleParserException {
       TextPosition start = getPosition();
       Path included = parseFileInputDependency(inputDependencyConsumer::acceptProguardInclude);
@@ -795,7 +813,7 @@
           .setOrigin(origin)
           .setStart(start);
       parseRuleTypeAndModifiers(keepRuleBuilder);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       if (keepRuleBuilder.getMemberRules().isEmpty()) {
         // If there are no member rules, a default rule for the parameterless constructor
         // applies. So we add that here.
@@ -817,7 +835,7 @@
       ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder()
           .setOrigin(origin)
           .setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -829,7 +847,7 @@
       ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder()
           .setOrigin(origin)
           .setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -840,7 +858,7 @@
         throws ProguardRuleParserException {
       ClassInlineRule.Builder keepRuleBuilder =
           ClassInlineRule.builder().setOrigin(origin).setStart(start).setType(type);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -851,7 +869,7 @@
         throws ProguardRuleParserException {
       NoFieldTypeStrengtheningRule.Builder keepRuleBuilder =
           NoFieldTypeStrengtheningRule.builder().setOrigin(origin).setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -862,7 +880,7 @@
         throws ProguardRuleParserException {
       NoUnusedInterfaceRemovalRule.Builder keepRuleBuilder =
           NoUnusedInterfaceRemovalRule.builder().setOrigin(origin).setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -873,7 +891,7 @@
         throws ProguardRuleParserException {
       NoVerticalClassMergingRule.Builder keepRuleBuilder =
           NoVerticalClassMergingRule.builder().setOrigin(origin).setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -884,7 +902,7 @@
         throws ProguardRuleParserException {
       NoHorizontalClassMergingRule.Builder keepRuleBuilder =
           NoHorizontalClassMergingRule.builder().setOrigin(origin).setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -894,7 +912,7 @@
     private <R extends NoOptimizationBaseRule<R>, B extends NoOptimizationBaseRule.Builder<R, B>>
         R parseNoOptimizationRule(Position start, B builder) throws ProguardRuleParserException {
       builder.setOrigin(origin).setStart(start);
-      parseClassSpec(builder, false);
+      parseClassSpec(builder);
       Position end = getPosition();
       builder.setSource(getSourceSnippet(contents, start, end));
       builder.setEnd(end);
@@ -906,7 +924,7 @@
         throws ProguardRuleParserException {
       MemberValuePropagationRule .Builder keepRuleBuilder =
           MemberValuePropagationRule.builder().setOrigin(origin).setStart(start).setType(type);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -919,7 +937,7 @@
           .setOrigin(origin)
           .setStart(start)
           .setType(type);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -932,7 +950,7 @@
           ProguardIdentifierNameStringRule.builder()
               .setOrigin(origin)
               .setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -944,7 +962,7 @@
       ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder()
           .setOrigin(origin)
           .setStart(optionStart);
-      parseClassSpec(ifRuleBuilder, false);
+      parseClassSpec(ifRuleBuilder);
 
       // Required a subsequent keep rule.
       skipWhitespace();
@@ -967,7 +985,7 @@
         throws ProguardRuleParserException {
       KeepConstantArgumentRule.Builder keepRuleBuilder =
           KeepConstantArgumentRule.builder().setOrigin(origin).setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -978,7 +996,7 @@
         throws ProguardRuleParserException {
       KeepUnusedArgumentRule.Builder keepRuleBuilder =
           KeepUnusedArgumentRule.builder().setOrigin(origin).setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -990,7 +1008,7 @@
         throws ProguardRuleParserException {
       ReprocessClassInitializerRule.Builder builder =
           ReprocessClassInitializerRule.builder().setOrigin(origin).setStart(start).setType(type);
-      parseClassSpec(builder, false);
+      parseClassSpec(builder);
       Position end = getPosition();
       builder.setSource(getSourceSnippet(contents, start, end));
       builder.setEnd(end);
@@ -1001,7 +1019,7 @@
         ReprocessMethodRule.Type type, Position start) throws ProguardRuleParserException {
       ReprocessMethodRule.Builder builder =
           ReprocessMethodRule.builder().setOrigin(origin).setStart(start).setType(type);
-      parseClassSpec(builder, false);
+      parseClassSpec(builder);
       Position end = getPosition();
       builder.setSource(getSourceSnippet(contents, start, end));
       builder.setEnd(end);
@@ -1012,7 +1030,7 @@
         throws ProguardRuleParserException {
       WhyAreYouNotInliningRule.Builder keepRuleBuilder =
           WhyAreYouNotInliningRule.builder().setOrigin(origin).setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
+      parseClassSpec(keepRuleBuilder);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
       keepRuleBuilder.setEnd(end);
@@ -1042,6 +1060,14 @@
       }
     }
 
+    private <
+            C extends ProguardClassSpecification,
+            B extends ProguardClassSpecification.Builder<C, B>>
+        void parseClassSpec(ProguardClassSpecification.Builder<C, B> builder)
+            throws ProguardRuleParserException {
+      parseClassSpec(builder, false);
+    }
+
     private
     <C extends ProguardClassSpecification, B extends ProguardClassSpecification.Builder<C, B>>
     void parseClassSpec(
@@ -1106,7 +1132,7 @@
             builder.getModifiersBuilder().setAllowsObfuscation(true);
           } else if (acceptString("accessmodification")) {
             builder.getModifiersBuilder().setAllowsAccessModification(true);
-          } else if (allowTestOptions) {
+          } else if (options.isTestingOptionsEnabled()) {
             if (acceptString("annotationremoval")) {
               builder.getModifiersBuilder().setAllowsAnnotationRemoval(true);
             }
@@ -1275,6 +1301,11 @@
               flags.setBridge();
             }
             break;
+          case 'c':
+            if ((found = acceptString("constructor"))) {
+              flags.setConstructor();
+            }
+            break;
           case 'f':
             if ((found = acceptString("final"))) {
               flags.setFinal();
@@ -1420,56 +1451,61 @@
                   }
                   skipWhitespace();
                   // Parse "return ..." if present.
+                  TextPosition returnStart = getPosition();
                   if (acceptString("return")) {
+                    if (!allowValueSpecification) {
+                      throw parseError("Unexpected value specification", returnStart);
+                    }
                     skipWhitespace();
-                    boolean anyReturnValueAttributesSkipped = parseIgnoredReturnValueAttributes();
-                    if (!anyReturnValueAttributesSkipped || !hasNextChar(';')) {
-                      if (acceptString("true")) {
-                        ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(true));
-                      } else if (acceptString("false")) {
-                        ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(false));
-                      } else if (acceptString("null")) {
-                        ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue());
-                      } else {
-                        TextPosition fieldOrValueStart = getPosition();
-                        String qualifiedFieldNameOrInteger = acceptFieldNameOrIntegerForReturn();
-                        if (qualifiedFieldNameOrInteger != null) {
-                          if (isInteger(qualifiedFieldNameOrInteger)) {
-                            Integer min = Integer.parseInt(qualifiedFieldNameOrInteger);
-                            Integer max = min;
-                            skipWhitespace();
-                            if (acceptString("..")) {
-                              skipWhitespace();
-                              max = acceptInteger();
-                              if (max == null) {
-                                throw parseError("Expected integer value");
-                              }
-                            }
-                            if (!allowValueSpecification) {
-                              throw parseError("Unexpected value specification", fieldOrValueStart);
-                            }
-                            ruleBuilder.setReturnValue(
-                                new ProguardMemberRuleReturnValue(new LongInterval(min, max)));
-                          } else {
-                            if (ruleBuilder.getTypeMatcher() instanceof MatchSpecificType) {
-                              int lastDotIndex = qualifiedFieldNameOrInteger.lastIndexOf(".");
-                              DexType fieldType =
-                                  ((MatchSpecificType) ruleBuilder.getTypeMatcher()).type;
-                              DexType fieldClass =
-                                  dexItemFactory.createType(
-                                      javaTypeToDescriptor(
-                                          qualifiedFieldNameOrInteger.substring(0, lastDotIndex)));
-                              DexString fieldName =
-                                  dexItemFactory.createString(
-                                      qualifiedFieldNameOrInteger.substring(lastDotIndex + 1));
-                              DexField field =
-                                  dexItemFactory.createField(fieldClass, fieldType, fieldName);
-                              ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(field));
-                            } else {
-                              throw parseError("Expected specific type", fieldOrValueStart);
-                            }
+                    if (acceptString("true")) {
+                      ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(true));
+                    } else if (acceptString("false")) {
+                      ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(false));
+                    } else if (acceptString("null")) {
+                      ruleBuilder.setReturnValue(
+                          new ProguardMemberRuleReturnValue(Nullability.definitelyNull()));
+                    } else {
+                      Integer integer = acceptInteger();
+                      if (integer != null) {
+                        Integer min = integer;
+                        Integer max = min;
+                        skipWhitespace();
+                        if (acceptString("..")) {
+                          skipWhitespace();
+                          max = acceptInteger();
+                          if (max == null) {
+                            throw parseError("Expected integer value");
                           }
                         }
+                        ruleBuilder.setReturnValue(
+                            new ProguardMemberRuleReturnValue(new LongInterval(min, max)));
+                      } else {
+                        Nullability nullability = Nullability.maybeNull();
+                        if (acceptString("_NONNULL_")) {
+                          nullability = Nullability.definitelyNotNull();
+                          skipWhitespace();
+                          if (acceptChar(';')) {
+                            ruleBuilder.setReturnValue(
+                                new ProguardMemberRuleReturnValue(nullability));
+                            return;
+                          }
+                        }
+                        String qualifiedFieldName = acceptQualifiedFieldName();
+                        if (qualifiedFieldName != null) {
+                          int lastDotIndex = qualifiedFieldName.lastIndexOf(".");
+                          DexType fieldHolder =
+                              dexItemFactory.createType(
+                                  javaTypeToDescriptor(
+                                      qualifiedFieldName.substring(0, lastDotIndex)));
+                          DexString fieldName =
+                              dexItemFactory.createString(
+                                  qualifiedFieldName.substring(lastDotIndex + 1));
+                          ruleBuilder.setReturnValue(
+                              new ProguardMemberRuleReturnValue(
+                                  fieldHolder, fieldName, nullability));
+                        } else {
+                          throw parseError("Expected qualified field");
+                        }
                       }
                     }
                   }
@@ -1670,7 +1706,7 @@
         throws ProguardRuleParserException {
       ProguardAssumeMayHaveSideEffectsRule.Builder builder =
           ProguardAssumeMayHaveSideEffectsRule.builder().setOrigin(origin).setStart(start);
-      parseClassSpec(builder, true);
+      parseClassSpec(builder);
       Position end = getPosition();
       builder.setSource(getSourceSnippet(contents, start, end));
       builder.setEnd(end);
@@ -1689,6 +1725,28 @@
       return builder.build();
     }
 
+    private CheckEnumUnboxedRule parseCheckEnumUnboxedRule(Position start)
+        throws ProguardRuleParserException {
+      CheckEnumUnboxedRule.Builder builder =
+          CheckEnumUnboxedRule.builder().setOrigin(origin).setStart(start);
+      parseClassSpec(builder);
+      Position end = getPosition();
+      builder.setSource(getSourceSnippet(contents, start, end));
+      builder.setEnd(end);
+      return builder.build();
+    }
+
+    private ConvertCheckNotNullRule parseConvertCheckNotNullRule(Position start)
+        throws ProguardRuleParserException {
+      ConvertCheckNotNullRule.Builder builder =
+          ConvertCheckNotNullRule.builder().setOrigin(origin).setStart(start);
+      parseClassSpec(builder);
+      Position end = getPosition();
+      builder.setSource(getSourceSnippet(contents, start, end));
+      builder.setEnd(end);
+      return builder.build();
+    }
+
     private void skipWhitespace() {
       while (!eof() && StringUtils.isWhitespace(peekChar())) {
         if (peekChar() == '\n') {
@@ -1978,27 +2036,35 @@
           contents.substring(start, end), wildcardsCollector.build(), negated);
     }
 
-    private String acceptFieldNameOrIntegerForReturn() {
+    private String acceptQualifiedFieldName() {
       skipWhitespace();
       int start = position;
-      int end = position;
+      // A qualified field name must be non empty.
+      if (eof(start)) {
+        return null;
+      }
+      // The first character of a qualified field name is an identifier part.
+      int firstCodePoint = contents.codePointAt(start);
+      if (!IdentifierUtils.isDexIdentifierStart(contents.codePointAt(start))) {
+        return null;
+      }
+      int end = start + Character.charCount(firstCodePoint);
       while (!eof(end)) {
-        int current = contents.codePointAt(end);
-        if (current == '.' && !eof(end + 1) && peekCharAt(end + 1) == '.') {
-          // The grammar is ambiguous. End accepting before .. token used in return ranges.
-          break;
-        }
-        if ((start == end && IdentifierUtils.isDexIdentifierStart(current))
-            || ((start < end)
-                && (IdentifierUtils.isDexIdentifierPart(current) || current == '.'))) {
-          end += Character.charCount(current);
+        int currentCodePoint = contents.codePointAt(end);
+        if (currentCodePoint == '.') {
+          end += 1;
+          // Each dot in a qualified field name must be followed by an identifier part.
+          if (!eof(end) && IdentifierUtils.isDexIdentifierPart(peekCharAt(end))) {
+            end += Character.charCount(contents.codePointAt(end));
+          } else {
+            break;
+          }
+        } else if (IdentifierUtils.isDexIdentifierPart(currentCodePoint)) {
+          end += Character.charCount(currentCodePoint);
         } else {
           break;
         }
       }
-      if (start == end) {
-        return null;
-      }
       position = end;
       return contents.substring(start, end);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
new file mode 100644
index 0000000..8811df7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2022, 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;
+
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault;
+
+public class ProguardConfigurationParserOptions {
+
+  private final boolean enableExperimentalCheckEnumUnboxed;
+  private final boolean enableExperimentalConvertCheckNotNull;
+  private final boolean enableExperimentalWhyAreYouNotInlining;
+  private final boolean enableTestingOptions;
+
+  ProguardConfigurationParserOptions(
+      boolean enableExperimentalCheckEnumUnboxed,
+      boolean enableExperimentalConvertCheckNotNull,
+      boolean enableExperimentalWhyAreYouNotInlining,
+      boolean enableTestingOptions) {
+    this.enableExperimentalCheckEnumUnboxed = enableExperimentalCheckEnumUnboxed;
+    this.enableExperimentalConvertCheckNotNull = enableExperimentalConvertCheckNotNull;
+    this.enableExperimentalWhyAreYouNotInlining = enableExperimentalWhyAreYouNotInlining;
+    this.enableTestingOptions = enableTestingOptions;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public boolean isExperimentalCheckEnumUnboxedEnabled() {
+    return enableExperimentalCheckEnumUnboxed;
+  }
+
+  public boolean isExperimentalConvertCheckNotNullEnabled() {
+    return enableExperimentalConvertCheckNotNull;
+  }
+
+  public boolean isExperimentalWhyAreYouNotInliningEnabled() {
+    return enableExperimentalWhyAreYouNotInlining;
+  }
+
+  public boolean isTestingOptionsEnabled() {
+    return enableTestingOptions;
+  }
+
+  public static class Builder {
+
+    private boolean enableExperimentalCheckEnumUnboxed;
+    private boolean enableExperimentalConvertCheckNotNull;
+    private boolean enableExperimentalWhyAreYouNotInlining;
+    private boolean enableTestingOptions;
+
+    public Builder readEnvironment() {
+      enableExperimentalCheckEnumUnboxed =
+          parseSystemPropertyOrDefault(
+              "com.android.tools.r8.experimental.enablecheckenumunboxed", false);
+      enableExperimentalConvertCheckNotNull =
+          parseSystemPropertyOrDefault(
+              "com.android.tools.r8.experimental.enableconvertchecknotnull", false);
+      enableExperimentalWhyAreYouNotInlining =
+          parseSystemPropertyOrDefault(
+              "com.android.tools.r8.experimental.enablewhyareyounotinlining", false);
+      enableTestingOptions =
+          parseSystemPropertyOrDefault("com.android.tools.r8.allowTestProguardOptions", false);
+      return this;
+    }
+
+    public Builder setEnableExperimentalCheckEnumUnboxed(
+        boolean enableExperimentalCheckEnumUnboxed) {
+      this.enableExperimentalCheckEnumUnboxed = enableExperimentalCheckEnumUnboxed;
+      return this;
+    }
+
+    public Builder setEnableExperimentalConvertCheckNotNull(
+        boolean enableExperimentalConvertCheckNotNull) {
+      this.enableExperimentalConvertCheckNotNull = enableExperimentalConvertCheckNotNull;
+      return this;
+    }
+
+    public Builder setEnableExperimentalWhyAreYouNotInlining(
+        boolean enableExperimentalWhyAreYouNotInlining) {
+      this.enableExperimentalWhyAreYouNotInlining = enableExperimentalWhyAreYouNotInlining;
+      return this;
+    }
+
+    public Builder setEnableTestingOptions(boolean enableTestingOptions) {
+      this.enableTestingOptions = enableTestingOptions;
+      return this;
+    }
+
+    public ProguardConfigurationParserOptions build() {
+      return new ProguardConfigurationParserOptions(
+          enableExperimentalCheckEnumUnboxed,
+          enableExperimentalConvertCheckNotNull,
+          enableExperimentalWhyAreYouNotInlining,
+          enableTestingOptions);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index bf21d1f..94ab239 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -4,89 +4,16 @@
 
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.LongInterval;
 import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
 import java.util.List;
-import java.util.stream.Collectors;
 
 public class ProguardConfigurationUtils {
 
-  private static Origin proguardCompatOrigin =
-      new Origin(Origin.root()) {
-        @Override
-        public String part() {
-          return "<PROGUARD_COMPATIBILITY_RULE>";
-        }
-      };
-
-  private static Origin synthesizedRecompilationOrigin =
-      new Origin(Origin.root()) {
-        @Override
-        public String part() {
-          return "<SYNTHESIZED_RECOMPILATION_RULE>";
-        }
-      };
-
-  public static ProguardKeepRule buildDefaultInitializerKeepRule(DexClass clazz) {
-    ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
-    builder.setOrigin(proguardCompatOrigin);
-    builder.setType(ProguardKeepRuleType.KEEP);
-    builder.getModifiersBuilder().setAllowsObfuscation(true);
-    builder.getModifiersBuilder().setAllowsOptimization(true);
-    builder.getClassAccessFlags().setVisibility(clazz.accessFlags);
-    builder.setClassType(ProguardClassType.CLASS);
-    builder.setClassNames(
-        ProguardClassNameList.singletonList(ProguardTypeMatcher.create(clazz.type)));
-    if (clazz.hasDefaultInitializer()) {
-      ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
-      memberRuleBuilder.setRuleType(ProguardMemberType.INIT);
-      memberRuleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
-      memberRuleBuilder.setArguments(ImmutableList.of());
-      builder.getMemberRules().add(memberRuleBuilder.build());
-    }
-    return builder.build();
-  }
-
-  public static ProguardKeepRule buildMethodKeepRule(DexClass clazz, DexEncodedMethod method) {
-    // TODO(b/122295241): These generated rules should be linked into the graph, eg, the method
-    // using identified reflection should be the source keeping the target alive.
-    assert clazz.type == method.getHolderType();
-    ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
-    builder.setOrigin(proguardCompatOrigin);
-    builder.setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
-    builder.getModifiersBuilder().setAllowsObfuscation(true);
-    builder.getModifiersBuilder().setAllowsOptimization(true);
-    builder.getClassAccessFlags().setVisibility(clazz.accessFlags);
-    if (clazz.isInterface()) {
-      builder.setClassType(ProguardClassType.INTERFACE);
-    } else {
-      builder.setClassType(ProguardClassType.CLASS);
-    }
-    builder.setClassNames(
-        ProguardClassNameList.singletonList(ProguardTypeMatcher.create(clazz.type)));
-    ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
-    memberRuleBuilder.setRuleType(ProguardMemberType.METHOD);
-    memberRuleBuilder.getAccessFlags().setFlags(method.accessFlags);
-    memberRuleBuilder.setName(
-        IdentifierPatternWithWildcards.withoutWildcards(method.getReference().name.toString()));
-    memberRuleBuilder.setTypeMatcher(
-        ProguardTypeMatcher.create(method.getReference().proto.returnType));
-    List<ProguardTypeMatcher> arguments =
-        Arrays.stream(method.getReference().proto.parameters.values)
-            .map(ProguardTypeMatcher::create)
-            .collect(Collectors.toList());
-    memberRuleBuilder.setArguments(arguments);
-    builder.getMemberRules().add(memberRuleBuilder.build());
-    return builder.build();
-  }
-
   public static ProguardAssumeNoSideEffectRule buildAssumeNoSideEffectsRuleForApiLevel(
       DexItemFactory factory, AndroidApiLevel apiLevel) {
     Origin synthesizedFromApiLevel =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
index 11b8868..9f7cbc7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
@@ -4,68 +4,99 @@
 
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.LongInterval;
 
 public class ProguardMemberRuleReturnValue {
-  public enum Type {
+
+  private enum Type {
     BOOLEAN,
-    VALUE_RANGE,
     FIELD,
-    NULL
+    NULLABILITY,
+    VALUE_RANGE
   }
 
   private final Type type;
   private final boolean booleanValue;
   private final LongInterval longInterval;
-  private final DexField field;
+  private final DexType fieldHolder;
+  private final DexString fieldName;
+  private final Nullability nullability;
 
   ProguardMemberRuleReturnValue(boolean value) {
     this.type = Type.BOOLEAN;
     this.booleanValue = value;
     this.longInterval = null;
-    this.field = null;
+    this.fieldHolder = null;
+    this.fieldName = null;
+    this.nullability = null;
+  }
+
+  @SuppressWarnings("InconsistentOverloads")
+  ProguardMemberRuleReturnValue(DexType fieldHolder, DexString fieldName, Nullability nullability) {
+    assert !nullability.isDefinitelyNull();
+    this.type = Type.FIELD;
+    this.booleanValue = false;
+    this.longInterval = null;
+    this.fieldHolder = fieldHolder;
+    this.fieldName = fieldName;
+    this.nullability = nullability;
+  }
+
+  ProguardMemberRuleReturnValue(Nullability nullability) {
+    assert nullability.isDefinitelyNull() || nullability.isDefinitelyNotNull();
+    this.type = Type.NULLABILITY;
+    this.booleanValue = false;
+    this.longInterval = null;
+    this.fieldHolder = null;
+    this.fieldName = null;
+    this.nullability = nullability;
   }
 
   ProguardMemberRuleReturnValue(LongInterval value) {
     this.type = Type.VALUE_RANGE;
     this.booleanValue = false;
     this.longInterval = value;
-    this.field = null;
+    this.fieldHolder = null;
+    this.fieldName = null;
+    this.nullability = getNullabilityForValueRange(value);
   }
 
-  ProguardMemberRuleReturnValue(DexField field) {
-    this.type = Type.FIELD;
-    this.booleanValue = false;
-    this.longInterval = null;
-    this.field = field;
-  }
-
-  ProguardMemberRuleReturnValue() {
-    this.type = Type.NULL;
-    this.booleanValue = false;
-    this.longInterval = null;
-    this.field = null;
+  private static Nullability getNullabilityForValueRange(LongInterval value) {
+    if (value.isSingleValue(0)) {
+      return Nullability.definitelyNull();
+    } else if (!value.containsValue(0)) {
+      return Nullability.definitelyNotNull();
+    } else {
+      return Nullability.maybeNull();
+    }
   }
 
   public boolean isBoolean() {
     return type == Type.BOOLEAN;
   }
 
-  public boolean isValueRange() {
-    return type == Type.VALUE_RANGE;
-  }
-
   public boolean isField() {
     return type == Type.FIELD;
   }
 
-  public boolean isNonNull() {
-    return isValueRange() && getValueRange().getMin() > 0;
+  public boolean isNullability() {
+    return type == Type.NULLABILITY;
   }
 
-  public boolean isNull() {
-    return type == Type.NULL;
+  public boolean isValueRange() {
+    return type == Type.VALUE_RANGE;
   }
 
   public boolean getBoolean() {
@@ -73,31 +104,23 @@
     return booleanValue;
   }
 
-  /**
-   * Returns if this return value is a single value.
-   *
-   * Boolean values and null are considered a single value.
-   */
-  public boolean isSingleValue() {
-    return isBoolean() || isNull() || (isValueRange() && longInterval.isSingleValue());
+  public DexType getFieldHolder() {
+    assert isField();
+    return fieldHolder;
   }
 
-  /**
-   * Returns the return value.
-   *
-   * Boolean values are returned as 0 for <code>false</code> and 1 for <code>true</code>.
-   *
-   * Reference value <code>null</code> is returned as 0.
-   */
-  public long getSingleValue() {
-    assert isSingleValue();
-    if (isBoolean()) {
-      return booleanValue ? 1 : 0;
-    }
-    if (isNull()) {
-      return 0;
-    }
-    return longInterval.getSingleValue();
+  public DexString getFieldName() {
+    assert isField();
+    return fieldName;
+  }
+
+  private boolean hasNullability() {
+    return isField() || isNullability() || isValueRange();
+  }
+
+  public Nullability getNullability() {
+    assert hasNullability();
+    return nullability;
   }
 
   public LongInterval getValueRange() {
@@ -105,9 +128,48 @@
     return longInterval;
   }
 
-  public DexField getField() {
-    assert isField();
-    return field;
+  public AbstractValue toAbstractValue(AppView<?> appView, DexType valueType) {
+    AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+    switch (type) {
+      case BOOLEAN:
+        return abstractValueFactory.createSingleNumberValue(BooleanUtils.intValue(booleanValue));
+
+      case FIELD:
+        DexClass holder = appView.definitionFor(fieldHolder);
+        if (holder != null) {
+          DexEncodedField field = holder.lookupUniqueStaticFieldWithName(fieldName);
+          if (field != null) {
+            return abstractValueFactory.createSingleFieldValue(
+                field.getReference(), ObjectState.empty());
+          }
+        }
+        return AbstractValue.unknown();
+
+      case NULLABILITY:
+        return nullability.isDefinitelyNull()
+            ? abstractValueFactory.createNullValue()
+            : AbstractValue.unknown();
+
+      case VALUE_RANGE:
+        if (valueType.isReferenceType()) {
+          return nullability.isDefinitelyNull()
+              ? abstractValueFactory.createNullValue()
+              : AbstractValue.unknown();
+        }
+        return longInterval.isSingleValue()
+            ? abstractValueFactory.createSingleNumberValue(longInterval.getSingleValue())
+            : abstractValueFactory.createNumberFromIntervalValue(
+                longInterval.getMin(), longInterval.getMax());
+
+      default:
+        throw new Unreachable("Unexpected type: " + type);
+    }
+  }
+
+  public DynamicType toDynamicType(AppView<?> appView, DexType valueType) {
+    return valueType.isReferenceType() && hasNullability() && getNullability().isDefinitelyNotNull()
+        ? DynamicType.definitelyNotNull()
+        : DynamicType.unknown();
   }
 
   @Override
@@ -115,18 +177,21 @@
     StringBuilder result = new StringBuilder();
     result.append(" return ");
     if (isBoolean()) {
-      result.append(booleanValue ? "true" : "false");
-    } else if (isNull()) {
-      result.append("null");
-    } else if (isValueRange()) {
+      result.append(booleanValue);
+    } else if (isField()) {
+      if (nullability.isDefinitelyNotNull()) {
+        result.append("_NONNULL_ ");
+      }
+      result.append(fieldHolder.getTypeName()).append('.').append(fieldName);
+    } else if (isNullability()) {
+      result.append(nullability.isDefinitelyNull() ? "null" : "_NONNULL_");
+    } else {
+      assert isValueRange();
       result.append(longInterval.getMin());
-      if (!isSingleValue()) {
+      if (!longInterval.isSingleValue()) {
         result.append("..");
         result.append(longInterval.getMax());
       }
-    } else {
-      assert isField();
-      result.append(field.holder.toSourceString() + '.' + field.name);
     }
     return result.toString();
   }
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 471f865..d804fa6 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
+import com.android.tools.r8.errors.AssumeValuesMissingStaticFieldDiagnostic;
 import com.android.tools.r8.errors.InlinableStaticFinalFieldPreconditionDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -45,9 +46,12 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringBaseEventConsumer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
 import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
@@ -105,6 +109,7 @@
   public static class RootSetBuilder {
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private AssumeInfoCollection.Builder assumeInfoCollectionBuilder;
     private final SubtypingInfo subtypingInfo;
     private final DirectMappedDexApplication application;
     private final Iterable<? extends ProguardConfigurationRule> rules;
@@ -127,8 +132,6 @@
         new IdentityHashMap<>();
     private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects =
         new IdentityHashMap<>();
-    private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
-    private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
     private final Set<DexMember<?, ?>> identifierNameStrings = Sets.newIdentityHashSet();
     private final Map<DexMethod, ProgramMethod> keptMethodBridges = new ConcurrentHashMap<>();
     private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
@@ -176,6 +179,12 @@
       // Intentionally empty.
     }
 
+    public RootSetBuilder setAssumeInfoCollectionBuilder(
+        AssumeInfoCollection.Builder assumeInfoCollectionBuilder) {
+      this.assumeInfoCollectionBuilder = assumeInfoCollectionBuilder;
+      return this;
+    }
+
     // Process a class with the keep rule.
     private void process(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
       if (!satisfyClassType(rule, clazz)) {
@@ -252,17 +261,23 @@
         throw new Unreachable("-if rule will be evaluated separately, not here.");
       } else if (rule.isProguardCheckDiscardRule()) {
         evaluateCheckDiscardRule(clazz, rule.asProguardCheckDiscardRule());
+      } else if (rule instanceof CheckEnumUnboxedRule) {
+        evaluateCheckEnumUnboxedRule(clazz, (CheckEnumUnboxedRule) rule);
       } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
         markClass(clazz, rule, ifRule);
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
         markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
-      } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
-          || rule instanceof ProguardAssumeNoSideEffectRule
-          || rule instanceof ProguardAssumeValuesRule) {
+      } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule) {
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
-        markMatchingOverriddenMethods(
-            appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
+        markMatchingOverriddenMethods(clazz, memberKeepRules, rule, null, true, ifRule);
         markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+      } else if (rule instanceof ProguardAssumeNoSideEffectRule
+          || rule instanceof ProguardAssumeValuesRule) {
+        if (assumeInfoCollectionBuilder != null) {
+          markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+          markMatchingOverriddenMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+          markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+        }
       } else if (rule instanceof NoFieldTypeStrengtheningRule) {
         markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
       } else if (rule instanceof InlineRule
@@ -287,10 +302,12 @@
       } else if (rule instanceof MemberValuePropagationRule) {
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
         markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
-      } else {
-        assert rule instanceof ProguardIdentifierNameStringRule;
+      } else if (rule instanceof ProguardIdentifierNameStringRule) {
         markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
         markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
+      } else {
+        assert rule instanceof ConvertCheckNotNullRule;
+        markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
       }
     }
 
@@ -349,7 +366,7 @@
       }
       finalizeCheckDiscardedInformation();
       generateAssumeNoSideEffectsWarnings();
-      if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
+      if (assumeInfoCollectionBuilder != null && !assumeInfoCollectionBuilder.isEmpty()) {
         BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
             .visit(appView.appInfo().classes(), this::propagateAssumeRules);
       }
@@ -381,8 +398,6 @@
           noHorizontalClassMerging,
           neverPropagateValue,
           mayHaveSideEffects,
-          noSideEffects,
-          assumedValues,
           dependentKeepClassCompatRule,
           identifierNameStrings,
           ifRules,
@@ -401,17 +416,12 @@
           assert !encodedMethod.shouldNotHaveCode();
           continue;
         }
-        propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, noSideEffects);
-        propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, assumedValues);
+        propagateAssumeRules(clazz, encodedMethod.getReference(), subTypes);
       }
     }
 
-    private void propagateAssumeRules(
-        DexType type,
-        DexMethod reference,
-        Set<DexType> subTypes,
-        Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) {
-      ProguardMemberRule ruleToBePropagated = null;
+    private void propagateAssumeRules(DexClass clazz, DexMethod reference, Set<DexType> subTypes) {
+      AssumeInfo infoToBePropagated = null;
       for (DexType subType : subTypes) {
         DexMethod referenceInSubType =
             appView.dexItemFactory().createMethod(subType, reference.proto, reference.name);
@@ -419,34 +429,34 @@
         // override the method, and when the retrieval of bound rule fails, it is unclear whether it
         // is due to the lack of the definition or it indeed means no matching rules. Similar to how
         // we apply those assume rules, here we use a resolved target.
-        DexEncodedMethod target =
+        DexClassAndMethod target =
             appView
                 .appInfo()
                 .unsafeResolveMethodDueToDexFormatLegacy(referenceInSubType)
-                .getSingleTarget();
+                .getResolutionPair();
         // But, the resolution should not be landed on the current type we are visiting.
-        if (target == null || target.getHolderType() == type) {
+        if (target == null || target.getHolder() == clazz) {
           continue;
         }
-        ProguardMemberRule ruleInSubType = assumeRulePool.get(target.getReference());
+        AssumeInfo ruleInSubType = assumeInfoCollectionBuilder.buildInfo(target);
         // We are looking for the greatest lower bound of assume rules from all sub types.
         // If any subtype doesn't have a matching assume rule, the lower bound is literally nothing.
         if (ruleInSubType == null) {
-          ruleToBePropagated = null;
+          infoToBePropagated = null;
           break;
         }
-        if (ruleToBePropagated == null) {
-          ruleToBePropagated = ruleInSubType;
+        if (infoToBePropagated == null) {
+          infoToBePropagated = ruleInSubType;
         } else {
           // TODO(b/133208961): Introduce comparison/meet of assume rules.
-          if (!ruleToBePropagated.equals(ruleInSubType)) {
-            ruleToBePropagated = null;
+          if (!infoToBePropagated.equals(ruleInSubType)) {
+            infoToBePropagated = null;
             break;
           }
         }
       }
-      if (ruleToBePropagated != null) {
-        assumeRulePool.put(reference, ruleToBePropagated);
+      if (infoToBePropagated != null) {
+        assumeInfoCollectionBuilder.meet(reference, infoToBePropagated);
       }
     }
 
@@ -663,7 +673,6 @@
     }
 
     private void markMatchingOverriddenMethods(
-        AppInfoWithClassHierarchy appInfoWithSubtyping,
         DexClass clazz,
         Collection<ProguardMemberRule> memberKeepRules,
         ProguardConfigurationRule rule,
@@ -1156,36 +1165,17 @@
           return;
         }
         evaluateKeepRule(
-            item.asProgramDefinition(), context.asProguardKeepRule(), rule, precondition, ifRule);
+            item.asProgramDefinition(), context.asProguardKeepRule(), precondition, ifRule);
       } else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
         mayHaveSideEffects.put(item.getReference(), rule);
         context.markAsUsed();
       } else if (context instanceof ProguardAssumeNoSideEffectRule) {
-        if (item.isMember()) {
-          DexClassAndMember<?, ?> member = item.asMember();
-          if (member.getHolderType() == appView.dexItemFactory().objectType) {
-            assert member.isMethod();
-            reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
-                member.asMethod(), (ProguardAssumeNoSideEffectRule) context);
-          } else {
-            noSideEffects.put(member.getReference(), rule);
-            if (member.isMethod()) {
-              DexClassAndMethod method = member.asMethod();
-              if (method.getDefinition().isClassInitializer()) {
-                feedback.classInitializerMayBePostponed(method.getDefinition());
-              }
-            }
-          }
-          context.markAsUsed();
-        }
+        evaluateAssumeNoSideEffectsRule(item, (ProguardAssumeNoSideEffectRule) context, rule);
+      } else if (context instanceof ProguardAssumeValuesRule) {
+        evaluateAssumeValuesRule(item, (ProguardAssumeValuesRule) context, rule);
       } else if (context instanceof ProguardWhyAreYouKeepingRule) {
         reasonAsked.computeIfAbsent(item.getReference(), i -> i);
         context.markAsUsed();
-      } else if (context instanceof ProguardAssumeValuesRule) {
-        if (item.isMember()) {
-          assumedValues.put(item.asMember().getReference(), rule);
-          context.markAsUsed();
-        }
       } else if (context.isProguardCheckDiscardRule()) {
         assert item.isProgramMember();
         evaluateCheckDiscardMemberRule(
@@ -1364,6 +1354,17 @@
             .asMethodJoiner()
             .disallowUnusedReturnValueOptimization();
         context.markAsUsed();
+      } else if (context instanceof ConvertCheckNotNullRule) {
+        assert item.isMethod();
+        feedback.setConvertCheckNotNull(item.asMethod());
+        if (item.isProgramMethod()) {
+          // Disallow optimization to prevent inlining.
+          dependentMinimumKeepInfo
+              .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+              .asMethodJoiner()
+              .disallowOptimization();
+        }
+        context.markAsUsed();
       } else {
         throw new Unreachable();
       }
@@ -1427,10 +1428,90 @@
       }
     }
 
+    private void evaluateAssumeNoSideEffectsRule(
+        Definition item, ProguardAssumeNoSideEffectRule context, ProguardMemberRule rule) {
+      assert assumeInfoCollectionBuilder != null;
+      if (!item.isMember()) {
+        return;
+      }
+      DexClassAndMember<?, ?> member = item.asMember();
+      if (member.getHolderType() == appView.dexItemFactory().objectType) {
+        assert member.isMethod();
+        reportAssumeNoSideEffectsWarningForJavaLangClassMethod(member.asMethod(), context);
+      } else {
+        DexType valueType =
+            member.getReference().apply(DexField::getType, DexMethod::getReturnType);
+        assumeInfoCollectionBuilder
+            .applyIf(
+                rule.hasReturnValue(),
+                builder -> {
+                  DynamicType assumeType = rule.getReturnValue().toDynamicType(appView, valueType);
+                  AbstractValue assumeValue =
+                      rule.getReturnValue().toAbstractValue(appView, valueType);
+                  builder.meetAssumeType(member, assumeType).meetAssumeValue(member, assumeValue);
+                  reportAssumeValuesWarningForMissingReturnField(context, rule, assumeValue);
+                })
+            .setIsSideEffectFree(member);
+        if (member.isMethod()) {
+          DexClassAndMethod method = member.asMethod();
+          if (method.getDefinition().isClassInitializer()) {
+            feedback.classInitializerMayBePostponed(method.getDefinition());
+          }
+        }
+      }
+      context.markAsUsed();
+    }
+
+    private void evaluateAssumeValuesRule(
+        Definition item, ProguardAssumeValuesRule context, ProguardMemberRule rule) {
+      assert assumeInfoCollectionBuilder != null;
+      if (!item.isMember() || !rule.hasReturnValue()) {
+        return;
+      }
+      DexClassAndMember<?, ?> member = item.asMember();
+      DexType valueType = member.getReference().apply(DexField::getType, DexMethod::getReturnType);
+      DynamicType assumeType = rule.getReturnValue().toDynamicType(appView, valueType);
+      AbstractValue assumeValue = rule.getReturnValue().toAbstractValue(appView, valueType);
+      assumeInfoCollectionBuilder
+          .meetAssumeType(member, assumeType)
+          .meetAssumeValue(member, assumeValue);
+      reportAssumeValuesWarningForMissingReturnField(context, rule, assumeValue);
+      context.markAsUsed();
+    }
+
+    private void evaluateCheckEnumUnboxedRule(DexClass clazz, CheckEnumUnboxedRule rule) {
+      if (clazz.isProgramClass()) {
+        if (clazz.isEnum()) {
+          dependentMinimumKeepInfo
+              .getOrCreateUnconditionalMinimumKeepInfo()
+              .getOrCreateMinimumKeepInfoFor(clazz.getType())
+              .asClassJoiner()
+              .setCheckEnumUnboxed();
+        } else {
+          StringDiagnostic warning =
+              new StringDiagnostic(
+                  "The rule `"
+                      + rule
+                      + "` matches the non-enum class "
+                      + clazz.getTypeName()
+                      + ".");
+          appView.reporter().warning(warning);
+        }
+      } else {
+        StringDiagnostic warning =
+            new StringDiagnostic(
+                "The rule `"
+                    + rule
+                    + "` matches the non-program class "
+                    + clazz.getTypeName()
+                    + ".");
+        appView.reporter().warning(warning);
+      }
+    }
+
     private void evaluateKeepRule(
         ProgramDefinition item,
         ProguardKeepRule context,
-        ProguardMemberRule rule,
         DexProgramClass precondition,
         ProguardIfRule ifRule) {
       if (item.isField()) {
@@ -1663,6 +1744,22 @@
                     .build());
           });
     }
+
+    private void reportAssumeValuesWarningForMissingReturnField(
+        ProguardConfigurationRule context, ProguardMemberRule rule, AbstractValue assumeValue) {
+      if (rule.hasReturnValue() && rule.getReturnValue().isField()) {
+        assert assumeValue.isSingleFieldValue() || assumeValue.isUnknown();
+        if (assumeValue.isUnknown()) {
+          ProguardMemberRuleReturnValue returnValue = rule.getReturnValue();
+          options.reporter.warning(
+              new AssumeValuesMissingStaticFieldDiagnostic.Builder()
+                  .setField(returnValue.getFieldHolder(), returnValue.getFieldName())
+                  .setOrigin(context.getOrigin())
+                  .setPosition(context.getPosition())
+                  .build());
+        }
+      }
+    }
   }
 
   abstract static class RootSetBase {
@@ -1712,8 +1809,6 @@
     public final Set<DexType> noHorizontalClassMerging;
     public final Set<DexMember<?, ?>> neverPropagateValue;
     public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
-    public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
-    public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
     public final Set<DexMember<?, ?>> identifierNameStrings;
     public final Set<ProguardIfRule> ifRules;
 
@@ -1733,8 +1828,6 @@
         Set<DexType> noHorizontalClassMerging,
         Set<DexMember<?, ?>> neverPropagateValue,
         Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
-        Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
-        Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         Set<DexMember<?, ?>> identifierNameStrings,
         Set<ProguardIfRule> ifRules,
@@ -1759,8 +1852,6 @@
       this.noHorizontalClassMerging = noHorizontalClassMerging;
       this.neverPropagateValue = neverPropagateValue;
       this.mayHaveSideEffects = mayHaveSideEffects;
-      this.noSideEffects = noSideEffects;
-      this.assumedValues = assumedValues;
       this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
       this.ifRules = Collections.unmodifiableSet(ifRules);
     }
@@ -1819,7 +1910,6 @@
       pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
       pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
       pruneDeadReferences(alwaysInline, definitions, enqueuer);
-      pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
     }
 
     private static void pruneDeadReferences(
@@ -1871,8 +1961,6 @@
           noHorizontalClassMerging,
           neverPropagateValue,
           mayHaveSideEffects,
-          noSideEffects,
-          assumedValues,
           dependentKeepClassCompatRule,
           identifierNameStrings,
           ifRules,
@@ -2044,8 +2132,6 @@
       StringBuilder builder = new StringBuilder();
       builder.append("RootSet");
       builder.append("\nreasonAsked: " + reasonAsked.size());
-      builder.append("\nnoSideEffects: " + noSideEffects.size());
-      builder.append("\nassumedValues: " + assumedValues.size());
       builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
       builder.append("\nifRules: " + ifRules.size());
       return builder.toString();
@@ -2164,8 +2250,6 @@
           Collections.emptySet(),
           emptyMap(),
           emptyMap(),
-          emptyMap(),
-          emptyMap(),
           Collections.emptySet(),
           ifRules,
           delayedRootSetActionItems,
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index f3391c8..7170817 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -124,6 +124,9 @@
           Log.debug(getClass(), "Removing class: " + clazz);
         }
         prunedTypes.add(clazz.type);
+        if (clazz.getSourceFile() != null) {
+          appView.addPrunedClassSourceFile(clazz.type, clazz.getSourceFile().toString());
+        }
         unusedItemsPrinter.registerUnusedClass(clazz);
       }
     }
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 0f2f684..eff85ec 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
@@ -9,6 +9,8 @@
 
 public class WhyAreYouNotInliningRule extends ProguardConfigurationRule {
 
+  public static final String RULE_NAME = "whyareyounotinlining";
+
   public static class Builder
       extends ProguardConfigurationRule.Builder<WhyAreYouNotInliningRule, Builder> {
 
@@ -76,6 +78,6 @@
 
   @Override
   String typeString() {
-    return "whyareyounotinlining";
+    return RULE_NAME;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 598dc4f..e9951c3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestHostClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import java.util.ArrayList;
@@ -171,6 +172,7 @@
             abstractFlag | finalFlag | itfFlag | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
     NestHostClassAttribute nestHost = null;
     List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
+    List<PermittedSubclassAttribute> permittedSubclasses = Collections.emptyList();
     EnclosingMethodAttribute enclosingMembers = null;
     List<InnerClassAttribute> innerClasses = Collections.emptyList();
     for (SyntheticMethodBuilder builder : methods) {
@@ -198,6 +200,7 @@
                 sourceFile,
                 nestHost,
                 nestMembers,
+                permittedSubclasses,
                 enclosingMembers,
                 innerClasses,
                 signature,
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 8eb6b51..374459a 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -78,6 +79,7 @@
 
   public static boolean isApiSafeForMemberRebinding(
       LibraryMethod method,
+      DexMethod original,
       AndroidApiLevelCompute androidApiLevelCompute,
       InternalOptions options) {
     ComputedApiLevel apiLevel =
@@ -87,6 +89,17 @@
       return false;
     }
     assert options.apiModelingOptions().enableApiCallerIdentification;
-    return apiLevel.asKnownApiLevel().getApiLevel().isLessThanOrEqualTo(options.getMinApiLevel());
+    ComputedApiLevel apiLevelOfOriginal =
+        androidApiLevelCompute.computeApiLevelForLibraryReference(
+            original, ComputedApiLevel.unknown());
+    if (apiLevelOfOriginal.isUnknownApiLevel()) {
+      return false;
+    }
+    return apiLevelOfOriginal
+        .asKnownApiLevel()
+        .max(apiLevel)
+        .asKnownApiLevel()
+        .getApiLevel()
+        .isLessThanOrEqualTo(options.getMinApiLevel());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/EntryUtils.java b/src/main/java/com/android/tools/r8/utils/EntryUtils.java
index e1d28b1..c096d00 100644
--- a/src/main/java/com/android/tools/r8/utils/EntryUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/EntryUtils.java
@@ -5,11 +5,12 @@
 package com.android.tools.r8.utils;
 
 import java.util.Map.Entry;
-import java.util.function.BiFunction;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 public class EntryUtils {
 
-  public static <K, V, R> R accept(Entry<K, V> entry, BiFunction<K, V, R> consumer) {
-    return consumer.apply(entry.getKey(), entry.getValue());
+  public static <K, V> Consumer<Entry<K, V>> accept(BiConsumer<K, V> consumer) {
+    return entry -> consumer.accept(entry.getKey(), entry.getValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index f3b82e4d..c836c46 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils;
 
 import static com.android.tools.r8.utils.AndroidApiLevel.ANDROID_PLATFORM;
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
 
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -395,6 +396,10 @@
   public boolean emitNestAnnotationsInDex =
       System.getProperty("com.android.tools.r8.emitNestAnnotationsInDex") != null;
 
+  // Flag to allow permitted subclasses annotations in DEX. See b/231930852 for context.
+  public boolean emitPermittedSubclassesAnnotationsInDex =
+      System.getProperty("com.android.tools.r8.emitPermittedSubclassesAnnotationsInDex") != null;
+
   // Contain the contents of the build properties file from the compiler command.
   public DumpOptions dumpOptions;
 
@@ -873,35 +878,6 @@
     return ImmutableSet.of();
   }
 
-  public static boolean isSystemPropertyForDevelopmentSet(String propertyName) {
-    if (Version.isDevelopmentVersion()) {
-      return System.getProperty(propertyName) != null;
-    }
-    return false;
-  }
-
-  public static String getSystemPropertyForDevelopmentOrDefault(
-      String propertyName, String defaultValue) {
-    if (Version.isDevelopmentVersion()) {
-      String propertyValue = System.getProperty(propertyName);
-      if (propertyValue != null) {
-        return propertyValue;
-      }
-    }
-    return defaultValue;
-  }
-
-  private static int parseSystemPropertyForDevelopmentOrDefault(
-      String propertyName, int defaultValue) {
-    if (Version.isDevelopmentVersion()) {
-      String propertyValue = System.getProperty(propertyName);
-      if (propertyValue != null) {
-        return Integer.parseInt(propertyValue);
-      }
-    }
-    return defaultValue;
-  }
-
   public static class InvalidParameterAnnotationInfo {
 
     final DexMethod method;
@@ -1416,7 +1392,7 @@
   public class InlinerOptions {
 
     public boolean enableInlining =
-        !isSystemPropertyForDevelopmentSet("com.android.tools.r8.disableinlining");
+        !parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.disableinlining", false);
 
     // This defines the limit of instructions in the inlinee
     public int simpleInliningInstructionLimit =
@@ -2030,6 +2006,10 @@
     return !isDesugaring();
   }
 
+  public boolean canUseSealedClasses() {
+    return !isDesugaring() || emitPermittedSubclassesAnnotationsInDex;
+  }
+
   public boolean canLeaveStaticInterfaceMethodInvokes() {
     return !isDesugaring() || hasMinApi(AndroidApiLevel.L);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index fb61406..389fd36 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.debuginfo.DebugRepresentation;
 import com.android.tools.r8.debuginfo.DebugRepresentation.DebugRepresentationPredicate;
 import com.android.tools.r8.dex.code.DexInstruction;
 import com.android.tools.r8.errors.Unreachable;
@@ -74,10 +75,9 @@
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
-import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -354,12 +354,10 @@
 
   private interface PcBasedDebugInfoRecorder {
     /** Callback to record a code object with a given max instruction PC and parameter count. */
-    void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount);
+    void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc);
 
     /** Callback to record a code object with only a single "line". */
-    void recordSingleLineFor(DexCode code, int parameterCount);
-
-    void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc);
+    void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc);
 
     /**
      * Install the correct debug info objects.
@@ -372,11 +370,33 @@
 
   private static class Pc2PcMappingSupport implements PcBasedDebugInfoRecorder {
 
-    // Some DEX VMs require matching parameter count in methods and debug info.
-    // Record the max pc for each parameter count so we can share the param count objects.
-    private Int2IntMap paramToMaxPc = new Int2IntOpenHashMap();
+    private static class UpdateInfo {
+      final DexCode code;
+      final int paramCount;
+      final int maxEncodingPc;
 
-    private final List<Pair<Integer, DexCode>> codesToUpdate = new ArrayList<>();
+      public UpdateInfo(DexCode code, int paramCount, int maxEncodingPc) {
+        this.code = code;
+        this.paramCount = paramCount;
+        this.maxEncodingPc = maxEncodingPc;
+      }
+
+      // Used as key when building the shared debug info map.
+      // Only param and max-pc are part of the key.
+
+      @Override
+      public boolean equals(Object o) {
+        UpdateInfo that = (UpdateInfo) o;
+        return paramCount == that.paramCount && maxEncodingPc == that.maxEncodingPc;
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(paramCount, maxEncodingPc);
+      }
+    }
+
+    private final List<UpdateInfo> codesToUpdate = new ArrayList<>();
 
     // We can only drop single-line debug info if it is OK to lose the source-file info.
     // This list is null if we must retain single-line entries.
@@ -387,46 +407,33 @@
     }
 
     @Override
-    public void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount) {
-      codesToUpdate.add(new Pair<>(parameterCount, code));
-      int existing = paramToMaxPc.getOrDefault(parameterCount, -1);
-      if (existing < lastInstructionPc) {
-        paramToMaxPc.put(parameterCount, lastInstructionPc);
-      }
+    public void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc) {
+      assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(code, maxEncodingPc);
+      codesToUpdate.add(new UpdateInfo(code, parameterCount, maxEncodingPc));
     }
 
     @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount) {
+    public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
       if (singleLineCodesToClear != null) {
         singleLineCodesToClear.add(code);
         return;
       }
-      int lastInstructionPc = ArrayUtils.last(code.instructions).getOffset();
-      recordPcMappingFor(code, lastInstructionPc, parameterCount);
-    }
-
-    @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) {
-      if (singleLineCodesToClear != null) {
-        singleLineCodesToClear.add(code);
-        return;
-      }
-      recordPcMappingFor(code, lastInstructionPc, parameterCount);
+      recordPcMappingFor(code, parameterCount, maxEncodingPc);
     }
 
     @Override
     public void updateDebugInfoInCodeObjects() {
-      Int2ReferenceMap<DexDebugInfo> debugInfos =
-          new Int2ReferenceOpenHashMap<>(paramToMaxPc.size());
+      Object2ReferenceMap<UpdateInfo, DexDebugInfo> debugInfos =
+          new Object2ReferenceOpenHashMap<>();
       codesToUpdate.forEach(
           entry -> {
-            int parameterCount = entry.getFirst();
-            DexCode code = entry.getSecond();
+            assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
+                entry.code, entry.maxEncodingPc);
             DexDebugInfo debugInfo =
                 debugInfos.computeIfAbsent(
-                    parameterCount,
-                    key -> buildPc2PcDebugInfo(paramToMaxPc.get(key), parameterCount));
-            code.setDebugInfo(debugInfo);
+                    entry, key -> buildPc2PcDebugInfo(key.maxEncodingPc, key.paramCount));
+            assert debugInfo.asPcBasedInfo().getMaxPc() == entry.maxEncodingPc;
+            entry.code.setDebugInfo(debugInfo);
           });
       if (singleLineCodesToClear != null) {
         singleLineCodesToClear.forEach(c -> c.setDebugInfo(null));
@@ -441,23 +448,18 @@
   private static class NativePcSupport implements PcBasedDebugInfoRecorder {
 
     @Override
-    public void recordPcMappingFor(DexCode code, int lastInstructionPc, int length) {
+    public void recordPcMappingFor(DexCode code, int length, int maxEncodingPc) {
       // Strip the info in full as the runtime will emit the PC directly.
       code.setDebugInfo(null);
     }
 
     @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount) {
+    public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
       // Strip the info at once as it does not conflict with any PC mapping update.
       code.setDebugInfo(null);
     }
 
     @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) {
-      recordSingleLineFor(code, parameterCount);
-    }
-
-    @Override
     public void updateDebugInfoInCodeObjects() {
       // Already null out the info so nothing to do.
     }
@@ -476,6 +478,7 @@
     ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
 
     Map<DexMethod, OutlineFixupBuilder> outlinesToFix = new IdentityHashMap<>();
+    Map<DexType, String> prunedInlinedClasses = new IdentityHashMap<>();
 
     PcBasedDebugInfoRecorder pcBasedDebugInfo =
         appView.options().canUseNativeDexPcInsteadOfDebugInfo()
@@ -563,21 +566,23 @@
           kotlinRemapper.currentMethod = definition;
           List<MappedPosition> mappedPositions;
           Code code = definition.getCode();
-          boolean canUseDexPc =
-              methods.size() == 1 && representation.useDexPcEncoding(clazz, definition);
+          int pcEncodingCutoff =
+              methods.size() == 1 ? representation.getDexPcEncodingCutoff(clazz, definition) : -1;
+          boolean canUseDexPc = pcEncodingCutoff > 0;
           if (code != null) {
-            if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
+            if (code.isDexCode()
+                && mustHaveResidualDebugInfo(code.asDexCode(), appView.options())) {
               if (canUseDexPc) {
                 mappedPositions =
                     optimizeDexCodePositionsForPc(
-                        definition, appView, kotlinRemapper, pcBasedDebugInfo);
+                        definition, appView, kotlinRemapper, pcBasedDebugInfo, pcEncodingCutoff);
               } else {
                 mappedPositions =
                     optimizeDexCodePositions(
                         definition, appView, kotlinRemapper, identityMapping, methods.size() != 1);
               }
             } else if (code.isCfCode()
-                && doesContainPositions(code.asCfCode())
+                && mustHaveResidualDebugInfo(code.asCfCode())
                 && !appView.isCfByteCodePassThrough(definition)) {
               mappedPositions = optimizeCfCodePositions(method, kotlinRemapper, appView);
             } else {
@@ -607,7 +612,7 @@
               && obfuscatedNameDexString == originalMethod.name
               && originalMethod.holder == originalType) {
             assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
-                || !doesContainPositions(definition)
+                || hasAtMostOnePosition(definition, appView.options())
                 || appView.isCfByteCodePassThrough(definition);
             continue;
           }
@@ -697,14 +702,15 @@
             ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.computeIfAbsent();
             MappedRange lastMappedRange =
                 getMappedRangesForPosition(
-                    appView.options().dexItemFactory(),
+                    appView,
                     getOriginalMethodSignature,
                     classNamingBuilder,
                     firstPosition.method,
                     obfuscatedName,
                     obfuscatedRange,
                     new Range(firstPosition.originalLine, lastPosition.originalLine),
-                    firstPosition.caller);
+                    firstPosition.caller,
+                    prunedInlinedClasses);
             for (MappingInformation info : methodMappingInfo) {
               lastMappedRange.addMappingInformation(info, Unreachable::raise);
             }
@@ -723,14 +729,15 @@
                     }
                     positionMap.put((int) line, placeHolderLineToBeFixed);
                     getMappedRangesForPosition(
-                        appView.options().dexItemFactory(),
+                        appView,
                         getOriginalMethodSignature,
                         classNamingBuilder,
                         position.getMethod(),
                         obfuscatedName,
                         new Range(placeHolderLineToBeFixed, placeHolderLineToBeFixed),
                         new Range(position.getLine(), position.getLine()),
-                        position.getCallerPosition());
+                        position.getCallerPosition(),
+                        prunedInlinedClasses);
                   });
               outlinesToFix
                   .computeIfAbsent(
@@ -743,7 +750,7 @@
               && definition.getCode().asDexCode().getDebugInfo()
                   == DexDebugInfoForSingleLineMethod.getInstance()) {
             pcBasedDebugInfo.recordSingleLineFor(
-                definition.getCode().asDexCode(), method.getParameters().size());
+                definition.getCode().asDexCode(), method.getParameters().size(), pcEncodingCutoff);
           }
         } // for each method of the group
       } // for each method group, grouped by name
@@ -755,9 +762,39 @@
     // Update all the debug-info objects.
     pcBasedDebugInfo.updateDebugInfoInCodeObjects();
 
+    // Add all pruned inline classes to the mapping to recover source files.
+    List<Entry<DexType, String>> prunedEntries = new ArrayList<>(prunedInlinedClasses.entrySet());
+    prunedEntries.sort(Entry.comparingByKey());
+    prunedEntries.forEach(
+        entry -> {
+          DexType holder = entry.getKey();
+          assert appView.appInfo().definitionForWithoutExistenceAssert(holder) == null;
+          String typeName = holder.toSourceString();
+          String sourceFile = entry.getValue();
+          assert !RetraceUtils.hasPredictableSourceFileName(typeName, sourceFile);
+          classNameMapperBuilder
+              .classNamingBuilder(
+                  typeName, typeName, com.android.tools.r8.position.Position.UNKNOWN)
+              .addMappingInformation(FileNameInformation.build(sourceFile), Unreachable::raise);
+        });
+
     return classNameMapperBuilder.build();
   }
 
+  private static boolean hasAtMostOnePosition(
+      DexEncodedMethod definition, InternalOptions options) {
+    if (!mustHaveResidualDebugInfo(definition, options)) {
+      return true;
+    }
+    Code code = definition.getCode();
+    if (code.isDexCode() && code.asDexCode().instructions.length == 1) {
+      // If the dex code is a single PC code then that also qualifies as having at most one
+      // position.
+      return true;
+    }
+    return false;
+  }
+
   private static DexMethod getOutlineMethod(MappedPosition mappedPosition) {
     if (mappedPosition.isOutline) {
       return mappedPosition.method;
@@ -770,14 +807,15 @@
   }
 
   private static MappedRange getMappedRangesForPosition(
-      DexItemFactory factory,
+      AppView<?> appView,
       Function<DexMethod, MethodSignature> getOriginalMethodSignature,
       Builder classNamingBuilder,
       DexMethod method,
       String obfuscatedName,
       Range obfuscatedRange,
       Range originalLine,
-      Position caller) {
+      Position caller,
+      Map<DexType, String> prunedInlineHolder) {
     MappedRange lastMappedRange =
         classNamingBuilder.addMappedRange(
             obfuscatedRange,
@@ -787,6 +825,13 @@
     int inlineFramesCount = 0;
     while (caller != null) {
       inlineFramesCount += 1;
+      String prunedClassSourceFileInfo =
+          appView.getPrunedClassSourceFileInfo(method.getHolderType());
+      if (prunedClassSourceFileInfo != null) {
+        String originalValue =
+            prunedInlineHolder.put(method.getHolderType(), prunedClassSourceFileInfo);
+        assert originalValue == null || originalValue.equals(prunedClassSourceFileInfo);
+      }
       lastMappedRange =
           classNamingBuilder.addMappedRange(
               obfuscatedRange,
@@ -798,7 +843,8 @@
             RewriteFrameMappingInformation.builder()
                 .addCondition(
                     ThrowsCondition.create(
-                        Reference.classFromDescriptor(factory.npeDescriptor.toString())))
+                        Reference.classFromDescriptor(
+                            appView.dexItemFactory().npeDescriptor.toString())))
                 .addRewriteAction(RemoveInnerFramesAction.create(inlineFramesCount))
                 .build(),
             Unreachable::raise);
@@ -919,13 +965,13 @@
     IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
         new IdentityHashMap<>(clazz.getMethodCollection().size());
     for (ProgramMethod programMethod : clazz.programMethods()) {
-      // Add method only if renamed, moved, or contains positions.
+      // Add method only if renamed, moved, or if it has debug info to map.
       DexEncodedMethod definition = programMethod.getDefinition();
       DexMethod method = programMethod.getReference();
       DexString renamedName = appView.getNamingLens().lookupName(method);
       if (renamedName != method.name
           || appView.graphLens().getOriginalMethodSignature(method) != method
-          || doesContainPositions(definition)
+          || mustHaveResidualDebugInfo(definition, appView.options())
           || definition.isD8R8Synthesized()) {
         methodsByRenamedName
             .computeIfAbsent(renamedName, key -> new ArrayList<>())
@@ -935,20 +981,26 @@
     return methodsByRenamedName;
   }
 
-  private static boolean doesContainPositions(DexEncodedMethod method) {
+  private static boolean mustHaveResidualDebugInfo(
+      DexEncodedMethod method, InternalOptions options) {
     Code code = method.getCode();
     if (code == null) {
       return false;
     }
     if (code.isDexCode()) {
-      return doesContainPositions(code.asDexCode());
+      return mustHaveResidualDebugInfo(code.asDexCode(), options);
     } else if (code.isCfCode()) {
-      return doesContainPositions(code.asCfCode());
+      return mustHaveResidualDebugInfo(code.asCfCode());
     }
     return false;
   }
 
-  public static boolean doesContainPositions(DexCode dexCode) {
+  public static boolean mustHaveResidualDebugInfo(DexCode dexCode, InternalOptions options) {
+    // All code objects must have debug info if discarding it is not allowed.
+    if (!options.allowDiscardingResidualDebugInfo()) {
+      return true;
+    }
+    // Otherwise debug info is only needed for code sequences with at least one position.
     DexDebugInfo debugInfo = dexCode.getDebugInfo();
     if (debugInfo == null) {
       return false;
@@ -964,7 +1016,7 @@
     return false;
   }
 
-  private static boolean doesContainPositions(CfCode cfCode) {
+  private static boolean mustHaveResidualDebugInfo(CfCode cfCode) {
     List<CfInstruction> instructions = cfCode.getInstructions();
     for (CfInstruction instruction : instructions) {
       if (instruction instanceof CfPosition) {
@@ -984,10 +1036,8 @@
     // Do the actual processing for each method.
     DexApplication application = appView.appInfo().app();
     DexCode dexCode = method.getCode().asDexCode();
-    // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
-    EventBasedDebugInfo debugInfo =
-        DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
-    assert debugInfo != null;
+    EventBasedDebugInfo debugInfo = getEventBasedDebugInfo(method, dexCode, appView);
+
     List<DexDebugEvent> processedEvents = new ArrayList<>();
 
     PositionEventEmitter positionEventEmitter =
@@ -1099,6 +1149,29 @@
     return mappedPositions;
   }
 
+  // This conversion *always* creates an event based debug info encoding as any non-info will
+  // be created as an implicit PC encoding.
+  private static EventBasedDebugInfo getEventBasedDebugInfo(
+      DexEncodedMethod method, DexCode dexCode, AppView<?> appView) {
+    // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
+    if (dexCode.getDebugInfo() == null) {
+      return createEventBasedInfoForMethodWithoutDebugInfo(method, appView.dexItemFactory());
+    }
+    assert method.getParameters().size() == dexCode.getDebugInfo().getParameterCount();
+    EventBasedDebugInfo debugInfo =
+        DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
+    assert debugInfo != null;
+    return debugInfo;
+  }
+
+  public static EventBasedDebugInfo createEventBasedInfoForMethodWithoutDebugInfo(
+      DexEncodedMethod method, DexItemFactory factory) {
+    return new EventBasedDebugInfo(
+        0,
+        new DexString[method.getParameters().size()],
+        new DexDebugEvent[] {factory.zeroChangeDefaultEvent});
+  }
+
   private static Position getPositionFromPositionState(DexDebugPositionState state) {
     PositionBuilder<?, ?> positionBuilder;
     if (state.getOutlineCallee() != null) {
@@ -1124,14 +1197,12 @@
       DexEncodedMethod method,
       AppView<?> appView,
       PositionRemapper positionRemapper,
-      PcBasedDebugInfoRecorder debugInfoProvider) {
+      PcBasedDebugInfoRecorder debugInfoProvider,
+      int pcEncodingCutoff) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     DexCode dexCode = method.getCode().asDexCode();
-    // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
-    EventBasedDebugInfo debugInfo =
-        DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
-    assert debugInfo != null;
+    EventBasedDebugInfo debugInfo = getEventBasedDebugInfo(method, dexCode, appView);
     IntBox firstDefaultEventPc = new IntBox(-1);
     BooleanBox singleOriginalLine = new BooleanBox(true);
     Pair<Integer, Position> lastPosition = new Pair<>();
@@ -1183,7 +1254,7 @@
       }
     }
 
-    int lastInstructionPc = ArrayUtils.last(dexCode.instructions).getOffset();
+    int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(dexCode).getOffset();
     if (lastPosition.getSecond() != null) {
       remapAndAddForPc(
           lastPosition.getFirst(),
@@ -1193,16 +1264,15 @@
           mappedPositions);
     }
 
-    // If we only have one original line we can always retrace back uniquely.
-    assert !mappedPositions.isEmpty();
+    assert !mappedPositions.isEmpty() || dexCode.instructions.length == 1;
     if (singleOriginalLine.isTrue()
         && lastPosition.getSecond() != null
-        && !mappedPositions.get(0).isOutlineCaller()) {
+        && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
       dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
       debugInfoProvider.recordSingleLineFor(
-          dexCode, method.getParameters().size(), lastInstructionPc);
+          dexCode, method.getParameters().size(), pcEncodingCutoff);
     } else {
-      debugInfoProvider.recordPcMappingFor(dexCode, lastInstructionPc, debugInfo.parameters.length);
+      debugInfoProvider.recordPcMappingFor(dexCode, debugInfo.parameters.length, pcEncodingCutoff);
     }
     return mappedPositions;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/LongInterval.java b/src/main/java/com/android/tools/r8/utils/LongInterval.java
index 7e721db..a05281a 100644
--- a/src/main/java/com/android/tools/r8/utils/LongInterval.java
+++ b/src/main/java/com/android/tools/r8/utils/LongInterval.java
@@ -36,6 +36,10 @@
     return min == max;
   }
 
+  public boolean isSingleValue(int value) {
+    return isSingleValue() && getSingleValue() == value;
+  }
+
   public long getSingleValue() {
     assert isSingleValue();
     return min;
diff --git a/src/main/java/com/android/tools/r8/utils/QuadConsumer.java b/src/main/java/com/android/tools/r8/utils/QuadConsumer.java
new file mode 100644
index 0000000..3249ce0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/QuadConsumer.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2022, 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.utils;
+
+public interface QuadConsumer<S, T, U, V> {
+
+  void accept(S s, T t, U u, V v);
+}
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 b53baff..59011c5 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -286,6 +286,14 @@
     return codePoint == BOM;
   }
 
+  public static boolean isFalsy(String string) {
+    return string.equals("0") || string.toLowerCase().equals("false");
+  }
+
+  public static boolean isTruthy(String string) {
+    return string.equals("1") || string.toLowerCase().equals("true");
+  }
+
   public static boolean isWhitespace(int codePoint) {
     return Character.isWhitespace(codePoint) || isBOM(codePoint);
   }
@@ -347,6 +355,10 @@
     return subject.replaceAll(Pattern.quote(target), Matcher.quoteReplacement(replacement));
   }
 
+  public static String quote(String string) {
+    return "\"" + string + "\"";
+  }
+
   public static String stacktraceAsString(Throwable throwable) {
     StringWriter sw = new StringWriter();
     throwable.printStackTrace(new PrintWriter(sw));
diff --git a/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java b/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java
new file mode 100644
index 0000000..d8d4a38
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, 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.utils;
+
+import static com.google.common.base.Predicates.alwaysTrue;
+
+import com.android.tools.r8.Version;
+import java.util.function.Predicate;
+
+public class SystemPropertyUtils {
+
+  public static String getSystemPropertyForDevelopment(String propertyName) {
+    return Version.isDevelopmentVersion() ? System.getProperty(propertyName) : null;
+  }
+
+  public static String getSystemPropertyForDevelopmentOrDefault(
+      String propertyName, String defaultValue) {
+    return Version.isDevelopmentVersion()
+        ? System.getProperty(propertyName, defaultValue)
+        : defaultValue;
+  }
+
+  public static boolean hasSystemPropertyThatMatches(
+      String propertyName, Predicate<String> predicate) {
+    String propertyValue = System.getProperty(propertyName);
+    return propertyValue != null && predicate.test(propertyValue);
+  }
+
+  public static boolean hasSystemPropertyForDevelopmentThatMatches(
+      String propertyName, Predicate<String> predicate) {
+    String propertyValue = getSystemPropertyForDevelopment(propertyName);
+    return propertyValue != null && predicate.test(propertyValue);
+  }
+
+  public static boolean isSystemPropertySet(String propertyName) {
+    return hasSystemPropertyThatMatches(propertyName, alwaysTrue());
+  }
+
+  public static boolean isSystemPropertyForDevelopmentSet(String propertyName) {
+    return hasSystemPropertyForDevelopmentThatMatches(propertyName, alwaysTrue());
+  }
+
+  public static boolean parseSystemPropertyOrDefault(String propertyName, boolean defaultValue) {
+    return internalParseSystemPropertyForDevelopmentOrDefault(
+        propertyName, System.getProperty(propertyName), defaultValue);
+  }
+
+  public static boolean parseSystemPropertyForDevelopmentOrDefault(
+      String propertyName, boolean defaultValue) {
+    return internalParseSystemPropertyForDevelopmentOrDefault(
+        propertyName, getSystemPropertyForDevelopment(propertyName), defaultValue);
+  }
+
+  private static boolean internalParseSystemPropertyForDevelopmentOrDefault(
+      String propertyName, String propertyValue, boolean defaultValue) {
+    if (propertyValue == null) {
+      return defaultValue;
+    }
+    if (StringUtils.isFalsy(propertyValue)) {
+      return false;
+    }
+    if (StringUtils.isTruthy(propertyValue)) {
+      return true;
+    }
+    throw new IllegalArgumentException(
+        "Expected value of " + propertyName + " to be a boolean, but was: " + propertyValue);
+  }
+
+  public static int parseSystemPropertyOrDefault(String propertyName, int defaultValue) {
+    String propertyValue = System.getProperty(propertyName);
+    return propertyValue != null ? Integer.parseInt(propertyValue) : defaultValue;
+  }
+
+  public static int parseSystemPropertyForDevelopmentOrDefault(
+      String propertyName, int defaultValue) {
+    String propertyValue = getSystemPropertyForDevelopment(propertyName);
+    return propertyValue != null ? Integer.parseInt(propertyValue) : defaultValue;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 2e7cd20..480f7b4 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.QuadConsumer;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
@@ -48,6 +49,36 @@
     Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
   }
 
+  protected void ensureCustomCheck(
+      QuadConsumer<ProcessResult, ProcessResult, ProcessResult, ProcessResult> checker,
+      String main,
+      AndroidApiLevel apiLevel,
+      List<String> args,
+      byte[]... classes)
+      throws Exception {
+    AndroidApp app = buildAndroidApp(classes);
+    Consumer<InternalOptions> setMinApiLevel = o -> o.setMinApiLevel(apiLevel);
+    ProcessResult javaResult = runOnJavaRaw(main, Arrays.asList(classes), args);
+    Consumer<ArtCommandBuilder> cmdBuilder =
+        builder -> {
+          for (String arg : args) {
+            builder.appendProgramArgument(arg);
+          }
+        };
+    ProcessResult d8Result =
+        runOnArtRaw(compileWithD8(app, setMinApiLevel), main, cmdBuilder, null);
+    ProcessResult r8Result =
+        runOnArtRaw(compileWithR8(app, setMinApiLevel), main, cmdBuilder, null);
+    ProcessResult r8ShakenResult =
+        runOnArtRaw(
+            compileWithR8(
+                app, keepMainProguardConfiguration(main) + "-dontobfuscate\n", setMinApiLevel),
+            main,
+            cmdBuilder,
+            null);
+    checker.accept(javaResult, d8Result, r8Result, r8ShakenResult);
+  }
+
   protected void ensureSameOutput(String main, byte[]... classes) throws Exception {
     AndroidApp app = buildAndroidApp(classes);
     ensureSameOutput(main, app, false, classes);
diff --git a/src/test/java/com/android/tools/r8/CheckEnumUnboxed.java b/src/test/java/com/android/tools/r8/CheckEnumUnboxed.java
new file mode 100644
index 0000000..11d8719
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/CheckEnumUnboxed.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface CheckEnumUnboxed {}
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 7b84c77..c9f0ee8 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -35,13 +35,10 @@
   private CompilationMode mode = CompilationMode.RELEASE;
   private String generatedKeepRules = null;
   private List<String> keepRules = new ArrayList<>();
-  private List<Path> additionalProgramFiles = new ArrayList<>();
-  private List<byte[]> additionalProgramClassFileData = new ArrayList<>();
+  private List<Path> programFiles = new ArrayList<>();
+  private List<byte[]> programClassFileData = new ArrayList<>();
   private Consumer<InternalOptions> optionsModifier = ConsumerUtils.emptyConsumer();
-  private Path desugarJDKLibs = ToolHelper.getDesugarJDKLibs();
-  private Path customConversions = null;
-  private StringResource desugaredLibrarySpecification =
-      StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting());
+  private StringResource desugaredLibrarySpecification = null;
   private List<Path> libraryFiles = new ArrayList<>();
   private ProgramConsumer programConsumer;
   private boolean finalPrefixVerification = true;
@@ -61,13 +58,18 @@
     return this;
   }
 
+  public L8TestBuilder addProgramFiles(Path... programFiles) {
+    this.programFiles.addAll(Arrays.asList(programFiles));
+    return this;
+  }
+
   public L8TestBuilder addProgramFiles(Collection<Path> programFiles) {
-    this.additionalProgramFiles.addAll(programFiles);
+    this.programFiles.addAll(programFiles);
     return this;
   }
 
   public L8TestBuilder addProgramClassFileData(byte[]... classes) {
-    this.additionalProgramClassFileData.addAll(Arrays.asList(classes));
+    this.programClassFileData.addAll(Arrays.asList(classes));
     return this;
   }
 
@@ -134,42 +136,16 @@
     return this;
   }
 
-  public L8TestBuilder setDesugarJDKLibs(Path desugarJDKLibs) {
-    assert desugarJDKLibs != null : "Use noDefaultDesugarJDKLibs to clear the default.";
-    this.desugarJDKLibs = desugarJDKLibs;
-    return this;
-  }
-
-  public L8TestBuilder noDefaultDesugarJDKLibs() {
-    this.desugarJDKLibs = null;
-    return this;
-  }
-
   public L8TestBuilder setProgramConsumer(ProgramConsumer programConsumer) {
     this.programConsumer = programConsumer;
     return this;
   }
 
-  public L8TestBuilder setDesugarJDKLibsCustomConversions(Path desugarJDKLibsConfiguration) {
-    this.customConversions = desugarJDKLibsConfiguration;
-    return this;
-  }
-
-  public L8TestBuilder setDesugaredLibraryConfiguration(Path path) {
+  public L8TestBuilder setDesugaredLibrarySpecification(Path path) {
     this.desugaredLibrarySpecification = StringResource.fromFile(path);
     return this;
   }
 
-  public L8TestBuilder setDesugaredLibraryConfiguration(StringResource configuration) {
-    this.desugaredLibrarySpecification = configuration;
-    return this;
-  }
-
-  public L8TestBuilder setDisableL8AnnotationRemoval(boolean disableL8AnnotationRemoval) {
-    return addOptionsModifier(
-        options -> options.disableL8AnnotationRemoval = disableL8AnnotationRemoval);
-  }
-
   private ProgramConsumer computeProgramConsumer(AndroidAppConsumers sink) {
     if (programConsumer != null) {
       return programConsumer;
@@ -185,7 +161,7 @@
     AndroidAppConsumers sink = new AndroidAppConsumers();
     L8Command.Builder l8Builder =
         L8Command.builder(state.getDiagnosticsHandler())
-            .addProgramFiles(getProgramFiles())
+            .addProgramFiles(programFiles)
             .addLibraryFiles(getLibraryFiles())
             .setMode(mode)
             .setIncludeClassesChecksum(true)
@@ -234,20 +210,8 @@
                                         || clazz.getFinalName().startsWith("java.")))));
   }
 
-  private Collection<Path> getProgramFiles() {
-    ImmutableList.Builder<Path> builder = ImmutableList.builder();
-    if (desugarJDKLibs != null) {
-      builder.add(desugarJDKLibs);
-    }
-    if (customConversions != null) {
-      builder.add(customConversions);
-    }
-    return builder.addAll(additionalProgramFiles).build();
-  }
-
   private L8Command.Builder addProgramClassFileData(L8Command.Builder builder) {
-    additionalProgramClassFileData.forEach(
-        data -> builder.addClassProgramData(data, Origin.unknown()));
+    programClassFileData.forEach(data -> builder.addClassProgramData(data, Origin.unknown()));
     return builder;
   }
 
diff --git a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
index 52cc764..1052657 100644
--- a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
+++ b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
@@ -4,146 +4,40 @@
 
 package com.android.tools.r8;
 
-import static com.android.tools.r8.LibraryDesugaringTestConfiguration.Configuration.DEFAULT;
-import static junit.framework.TestCase.assertNotNull;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 
 public class LibraryDesugaringTestConfiguration {
 
-  private static final String RELEASES_DIR = "third_party/openjdk/desugar_jdk_libs_releases/";
-
-  public enum Configuration {
-    DEFAULT(
-        ToolHelper.getDesugarJDKLibs(),
-        ToolHelper.DESUGAR_LIB_CONVERSIONS,
-        ToolHelper.getDesugarLibJsonForTesting()),
-    DEFAULT_JDK11(
-        Paths.get("third_party/openjdk/desugar_jdk_libs_11/desugar_jdk_libs.jar"),
-        ToolHelper.DESUGAR_LIB_CONVERSIONS,
-        Paths.get("src/library_desugar/jdk11/desugar_jdk_libs.json")),
-    RELEASED_1_0_9("1.0.9"),
-    RELEASED_1_0_10("1.0.10"),
-    RELEASED_1_1_0("1.1.0"),
-    RELEASED_1_1_1("1.1.1"),
-    RELEASED_1_1_5("1.1.5");
-
-    private final Path desugarJdkLibs;
-    private final Path customConversions;
-    private final Path configuration;
-
-    Configuration(Path desugarJdkLibs, Path customConversions, Path configuration) {
-      this.desugarJdkLibs = desugarJdkLibs;
-      this.customConversions = customConversions;
-      this.configuration = configuration;
-    }
-
-    Configuration(String version) {
-      this(
-          Paths.get(RELEASES_DIR, version, "desugar_jdk_libs.jar"),
-          Paths.get(RELEASES_DIR, version, "desugar_jdk_libs_configuration.jar"),
-          Paths.get(RELEASES_DIR, version, "desugar.json"));
-    }
-
-    public static List<Configuration> getReleased() {
-      return ImmutableList.of(
-          RELEASED_1_0_9, RELEASED_1_0_10, RELEASED_1_1_0, RELEASED_1_1_1, RELEASED_1_1_5);
-    }
-  }
-
-  private final AndroidApiLevel minApiLevel;
-  private final Path desugarJdkLibs;
-  private final Path customConversions;
   private final List<StringResource> desugaredLibrarySpecificationResources;
-  private final boolean withKeepRuleConsumer;
   private final KeepRuleConsumer keepRuleConsumer;
-  private final CompilationMode mode;
-  private final boolean addRunClassPath;
 
   public static final LibraryDesugaringTestConfiguration DISABLED =
       new LibraryDesugaringTestConfiguration();
 
   private LibraryDesugaringTestConfiguration() {
-    this.minApiLevel = null;
-    this.desugarJdkLibs = null;
-    this.customConversions = null;
     this.keepRuleConsumer = null;
-    this.withKeepRuleConsumer = false;
     this.desugaredLibrarySpecificationResources = null;
-    this.mode = null;
-    this.addRunClassPath = false;
   }
 
   private LibraryDesugaringTestConfiguration(
-      AndroidApiLevel minApiLevel,
-      Path desugarJdkLibs,
-      Path customConversions,
       List<StringResource> desugaredLibrarySpecificationResources,
-      boolean withKeepRuleConsumer,
-      KeepRuleConsumer keepRuleConsumer,
-      CompilationMode mode,
-      boolean addRunClassPath) {
-    this.minApiLevel = minApiLevel;
-    this.desugarJdkLibs = desugarJdkLibs;
-    this.customConversions = customConversions;
+      KeepRuleConsumer keepRuleConsumer) {
     this.desugaredLibrarySpecificationResources = desugaredLibrarySpecificationResources;
-    this.withKeepRuleConsumer = withKeepRuleConsumer;
     this.keepRuleConsumer = keepRuleConsumer;
-    this.mode = mode;
-    this.addRunClassPath = addRunClassPath;
   }
 
   public static class Builder {
 
-    AndroidApiLevel minApiLevel;
-    private Path desugarJdkLibs;
-    private Path customConversions;
     private final List<StringResource> desugaredLibrarySpecificationResources = new ArrayList<>();
-    boolean withKeepRuleConsumer = false;
     KeepRuleConsumer keepRuleConsumer;
-    private CompilationMode mode = CompilationMode.DEBUG;
-    boolean addRunClassPath = true;
-
     private Builder() {}
 
-    public Builder setMinApi(AndroidApiLevel minApiLevel) {
-      this.minApiLevel = minApiLevel;
-      return this;
-    }
-
-    public Builder setConfiguration(Configuration configuration) {
-      desugarJdkLibs = configuration.desugarJdkLibs;
-      customConversions = configuration.customConversions;
-      desugaredLibrarySpecificationResources.clear();
-      desugaredLibrarySpecificationResources.add(
-          StringResource.fromFile(configuration.configuration));
-      return this;
-    }
-
-    public Builder withKeepRuleConsumer() {
-      withKeepRuleConsumer = true;
-      return this;
-    }
-
     public Builder setKeepRuleConsumer(KeepRuleConsumer keepRuleConsumer) {
-      withKeepRuleConsumer = false;
-      if (keepRuleConsumer == null) {
-        this.keepRuleConsumer = null;
-      } else {
-        this.keepRuleConsumer = keepRuleConsumer;
-      }
+      this.keepRuleConsumer = keepRuleConsumer;
       return this;
     }
 
@@ -152,33 +46,10 @@
       return this;
     }
 
-    public Builder setMode(CompilationMode mode) {
-      this.mode = mode;
-      return this;
-    }
-
-    public Builder dontAddRunClasspath() {
-      addRunClassPath = false;
-      return this;
-    }
-
     public LibraryDesugaringTestConfiguration build() {
-      if (desugaredLibrarySpecificationResources.isEmpty()) {
-        desugaredLibrarySpecificationResources.add(
-            StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()));
-      }
-      if (withKeepRuleConsumer) {
-        this.keepRuleConsumer = createKeepRuleConsumer(minApiLevel);
-      }
+      assert !desugaredLibrarySpecificationResources.isEmpty();
       return new LibraryDesugaringTestConfiguration(
-          minApiLevel,
-          desugarJdkLibs != null ? desugarJdkLibs : DEFAULT.desugarJdkLibs,
-          customConversions != null ? customConversions : DEFAULT.customConversions,
-          desugaredLibrarySpecificationResources,
-          withKeepRuleConsumer,
-          keepRuleConsumer,
-          mode,
-          addRunClassPath);
+          desugaredLibrarySpecificationResources, keepRuleConsumer);
     }
   }
 
@@ -186,18 +57,16 @@
     return new Builder();
   }
 
-  public static LibraryDesugaringTestConfiguration forApiLevel(AndroidApiLevel apiLevel) {
-    return LibraryDesugaringTestConfiguration.builder().setMinApi(apiLevel).build();
+  public static LibraryDesugaringTestConfiguration forSpecification(Path specification) {
+    return LibraryDesugaringTestConfiguration.builder()
+        .addDesugaredLibraryConfiguration(StringResource.fromFile(specification))
+        .build();
   }
 
   public boolean isEnabled() {
     return this != DISABLED;
   }
 
-  public boolean isAddRunClassPath() {
-    return addRunClassPath;
-  }
-
   public void configure(D8Command.Builder builder) {
     if (!isEnabled()) {
       return;
@@ -218,51 +87,6 @@
     desugaredLibrarySpecificationResources.forEach(builder::addDesugaredLibraryConfiguration);
   }
 
-  public Path buildDesugaredLibrary(TestState state) {
-    String generatedKeepRules = null;
-    if (withKeepRuleConsumer) {
-      if (keepRuleConsumer instanceof PresentKeepRuleConsumer) {
-        generatedKeepRules = keepRuleConsumer.get();
-        assertNotNull(generatedKeepRules);
-      } else {
-        assertThat(keepRuleConsumer, instanceOf(AbsentKeepRuleConsumer.class));
-      }
-    }
-    String finalGeneratedKeepRules = generatedKeepRules;
-    try {
-      assert desugaredLibrarySpecificationResources.size() == 1 : "There can be only one";
-      return L8TestBuilder.create(minApiLevel, Backend.DEX, state)
-          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-          .setDesugarJDKLibs(desugarJdkLibs)
-          .setDesugarJDKLibsCustomConversions(customConversions)
-          .setDesugaredLibraryConfiguration(desugaredLibrarySpecificationResources.get(0))
-          .applyIf(
-              mode == CompilationMode.RELEASE,
-              builder -> {
-                if (finalGeneratedKeepRules != null && !finalGeneratedKeepRules.trim().isEmpty()) {
-                  builder.addGeneratedKeepRules(finalGeneratedKeepRules);
-                }
-              },
-              L8TestBuilder::setDebug)
-          .compile()
-          .writeToZip();
-    } catch (CompilationFailedException | ExecutionException | IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  public KeepRuleConsumer getKeepRuleConsumer() {
-    return keepRuleConsumer;
-  }
-
-  public static KeepRuleConsumer createKeepRuleConsumer(TestParameters parameters) {
-    return createKeepRuleConsumer(parameters.getApiLevel());
-  }
-
-  private static KeepRuleConsumer createKeepRuleConsumer(AndroidApiLevel apiLevel) {
-    return new PresentKeepRuleConsumer();
-  }
-
   public static class PresentKeepRuleConsumer implements KeepRuleConsumer {
 
     StringBuilder stringBuilder = new StringBuilder();
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index ff90c43..ebe8352 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -10,12 +10,12 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.CheckEnumUnboxedRule;
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
 import com.android.tools.r8.shaking.KeepUnusedReturnValueRule;
 import com.android.tools.r8.shaking.NoFieldTypeStrengtheningRule;
@@ -28,7 +28,6 @@
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -500,6 +499,12 @@
     return addOptionsModification(options -> options.testing.allowInliningOfSynthetics = false);
   }
 
+  public T enableCheckEnumUnboxedAnnotations() {
+    return addCheckEnumUnboxedAnnotation()
+        .addInternalMatchInterfaceRule(CheckEnumUnboxedRule.RULE_NAME, CheckEnumUnboxed.class)
+        .enableExperimentalCheckEnumUnboxed();
+  }
+
   public T enableKeepUnusedReturnValueAnnotations() {
     return addKeepUnusedReturnValueAnnotation()
         .addInternalMatchAnnotationOnMethodRule(
@@ -657,8 +662,23 @@
     return self();
   }
 
+  public T enableExperimentalCheckEnumUnboxed() {
+    builder.setEnableExperimentalCheckEnumUnboxed();
+    return self();
+  }
+
+  public T enableExperimentalConvertCheckNotNull() {
+    builder.setEnableExperimentalConvertCheckNotNull();
+    return self();
+  }
+
+  public T enableExperimentalWhyAreYouNotInlining() {
+    builder.setEnableExperimentalWhyAreYouNotInlining();
+    return self();
+  }
+
   public T enableProguardTestOptions() {
-    builder.allowTestProguardOptions();
+    builder.setEnableTestProguardOptions();
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 48f9fc5..4d557ea 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -198,10 +198,6 @@
   public RR run(TestRuntime runtime, String mainClass, String... args)
       throws ExecutionException, IOException {
     assert getBackend() == runtime.getBackend();
-    if (libraryDesugaringTestConfiguration.isEnabled()
-        && libraryDesugaringTestConfiguration.isAddRunClassPath()) {
-      additionalRunClassPath.add(libraryDesugaringTestConfiguration.buildDesugaredLibrary(state));
-    }
     ClassSubject mainClassSubject = inspector().clazz(mainClass);
     if (!mainClassSubject.isPresent()) {
       for (Path classpathFile : additionalRunClassPath) {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index a5c5302..58e5901 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -436,6 +436,10 @@
     return addTestingAnnotation(AssumeNoSideEffects.class);
   }
 
+  public final T addCheckEnumUnboxedAnnotation() {
+    return addTestingAnnotation(CheckEnumUnboxed.class);
+  }
+
   public final T addConstantArgumentAnnotations() {
     return addTestingAnnotation(KeepConstantArguments.class);
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e7c64cd..eb7df8c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -184,28 +184,13 @@
       OPEN_JDK_DIR + "desugar_jdk_libs_releases/";
   public static final Path DESUGARED_JDK_8_LIB_JAR =
       Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs/desugar_jdk_libs.jar");
-  public static final Path UNDESUGARED_JDK_11_LIB_JAR =
-      DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
-          Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar"));
+  public static final Path DESUGARED_JDK_11_LIB_JAR =
+      Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar");
 
-  public static Path getDesugarJDKLibs() {
-    return DesugaredLibraryJDK11Undesugarer.undesugaredJar();
-  }
-
-  public static Path getDesugarJDKLibsBazelGeneratedFile() {
-    String property = System.getProperty("desugar_jdk_libs");
-    if (property == null) {
-      return DESUGARED_JDK_8_LIB_JAR;
-    }
-    return Paths.get(property);
-  }
-
-  private static String getDesugarLibraryJsonDir() {
-    return System.getProperty("desugar_jdk_json_dir", "src/library_desugar");
-  }
-
-  public static Path getDesugarLibJsonForTesting() {
-    return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs.json");
+  public static Path getUndesugaredJdk11LibJarForTesting() {
+    return DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
+        Paths.get("build/libs"),
+        Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar"));
   }
 
   public static boolean isLocalDevelopment() {
@@ -2227,7 +2212,7 @@
   }
 
   public static R8Command.Builder allowTestProguardOptions(R8Command.Builder builder) {
-    builder.allowTestProguardOptions();
+    builder.setEnableTestProguardOptions();
     return builder;
   }
 
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
new file mode 100644
index 0000000..db640a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
@@ -0,0 +1,230 @@
+// Copyright (c) 2022, 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.androidapi;
+
+import static com.android.tools.r8.apimodel.JavaSourceCodePrinter.Type.fromType;
+import static com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer.isCovariantReturnTypeAnnotation;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter.JavaSourceCodeMethodPrinter;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter.KnownType;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter.MethodParameter;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter.ParameterizedType;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.EntryUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+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 GenerateCovariantReturnTypeMethodsTest extends TestBase {
+
+  private static final String CLASS_NAME = "CovariantReturnTypeMethods";
+  private static final String PACKAGE_NAME = "com.android.tools.r8.androidapi";
+  // When updating to support a new api level build libcore in aosp and update the cloud dependency.
+  private static final Path PATH_TO_CORE_JAR =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "libcore_latest", "core-oj.jar");
+  private static final Path DESTINATION_FILE =
+      Paths.get(ToolHelper.SOURCE_DIR)
+          .resolve(PACKAGE_NAME.replace('.', '/'))
+          .resolve(CLASS_NAME + ".java");
+  private static final AndroidApiLevel GENERATED_FOR_API_LEVEL = AndroidApiLevel.T;
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void testLibCoreNeedsUpgrading() {
+    assertEquals(GENERATED_FOR_API_LEVEL, AndroidApiLevel.LATEST);
+  }
+
+  @Test
+  public void testCanFindAnnotatedMethodsInJar() throws Exception {
+    CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
+    // These assertions are here to ensure we produce a sane result.
+    assertEquals(51, covariantMethodsInJar.methodReferenceMap.size());
+  }
+
+  @Test
+  public void testGeneratedCodeUpToDate() throws Exception {
+    assertEquals(FileUtils.readTextFile(DESTINATION_FILE, StandardCharsets.UTF_8), generateCode());
+  }
+
+  public static String generateCode() throws Exception {
+    CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
+    Map<MethodReference, List<MethodReference>> methodReferenceMap =
+        covariantMethodsInJar.methodReferenceMap;
+    List<Entry<MethodReference, List<MethodReference>>> entries =
+        new ArrayList<>(methodReferenceMap.entrySet());
+    entries.sort(Entry.comparingByKey(MethodReferenceUtils.getMethodReferenceComparator()));
+    JavaSourceCodePrinter printer =
+        JavaSourceCodePrinter.builder()
+            .setHeader(
+                MethodGenerationBase.getHeaderString(
+                    2022, GenerateCovariantReturnTypeMethodsTest.class.getSimpleName()))
+            .setPackageName(PACKAGE_NAME)
+            .setClassName(CLASS_NAME)
+            .build();
+    String javaSourceCode =
+        printer
+            .addMethod(
+                "public static",
+                null,
+                "registerMethodsWithCovariantReturnType",
+                ImmutableList.of(
+                    MethodParameter.build(fromType(KnownType.DexItemFactory), "factory"),
+                    MethodParameter.build(
+                        ParameterizedType.fromType(
+                            KnownType.Consumer, fromType(KnownType.DexMethod)),
+                        "consumer")),
+                methodPrinter ->
+                    entries.forEach(
+                        EntryUtils.accept(
+                            (ignored, covariations) ->
+                                covariations.forEach(
+                                    covariant ->
+                                        registerCovariantMethod(methodPrinter, covariant)))))
+            .toString();
+    Path tempFile = Files.createTempFile("output-", ".java");
+    Files.write(tempFile, javaSourceCode.getBytes(StandardCharsets.UTF_8));
+    return MethodGenerationBase.formatRawOutput(tempFile);
+  }
+
+  private static void registerCovariantMethod(
+      JavaSourceCodeMethodPrinter methodPrinter, MethodReference covariant) {
+    methodPrinter
+        .addInstanceMethodCall(
+            "consumer",
+            "accept",
+            () ->
+                methodPrinter.addInstanceMethodCall(
+                    "factory",
+                    "createMethod",
+                    callCreateType(methodPrinter, covariant.getHolderClass().getDescriptor()),
+                    callCreateProto(
+                        methodPrinter,
+                        covariant.getReturnType().getDescriptor(),
+                        covariant.getFormalTypes().stream()
+                            .map(TypeReference::getDescriptor)
+                            .collect(Collectors.toList())),
+                    methodPrinter.literal(covariant.getMethodName())))
+        .addSemicolon()
+        .newLine();
+  }
+
+  private static Action callCreateType(
+      JavaSourceCodeMethodPrinter methodPrinter, String descriptor) {
+    return () ->
+        methodPrinter.addInstanceMethodCall(
+            "factory", "createType", methodPrinter.literal(descriptor));
+  }
+
+  private static Action callCreateProto(
+      JavaSourceCodeMethodPrinter methodPrinter,
+      String returnTypeDescriptor,
+      Collection<String> args) {
+    List<Action> actionList = new ArrayList<>();
+    actionList.add(callCreateType(methodPrinter, returnTypeDescriptor));
+    for (String arg : args) {
+      actionList.add(callCreateType(methodPrinter, arg));
+    }
+    return () -> methodPrinter.addInstanceMethodCall("factory", "createProto", actionList);
+  }
+
+  public static void main(String[] args) throws Exception {
+    Files.write(DESTINATION_FILE, generateCode().getBytes(StandardCharsets.UTF_8));
+  }
+
+  public static class CovariantMethodsInJarResult {
+    private final Map<MethodReference, List<MethodReference>> methodReferenceMap;
+
+    private CovariantMethodsInJarResult(
+        Map<MethodReference, List<MethodReference>> methodReferenceMap) {
+      this.methodReferenceMap = methodReferenceMap;
+    }
+
+    public static CovariantMethodsInJarResult create() throws Exception {
+      Map<MethodReference, List<MethodReference>> methodReferenceMap = new HashMap<>();
+      CodeInspector inspector = new CodeInspector(PATH_TO_CORE_JAR);
+      DexItemFactory factory = inspector.getFactory();
+      for (FoundClassSubject clazz : inspector.allClasses()) {
+        clazz.forAllMethods(
+            method -> {
+              List<DexAnnotation> covariantAnnotations =
+                  inspector.findAnnotations(
+                      method.getMethod().annotations(),
+                      annotation ->
+                          isCovariantReturnTypeAnnotation(annotation.annotation, factory));
+              if (!covariantAnnotations.isEmpty()) {
+                MethodReference methodReference = method.asMethodReference();
+                for (DexAnnotation covariantAnnotation : covariantAnnotations) {
+                  if (covariantAnnotation.annotation.type
+                      == factory.annotationCovariantReturnType) {
+                    createCovariantMethodReference(
+                        methodReference, covariantAnnotation, methodReferenceMap);
+                  } else {
+                    fail("There are no such annotations present in libcore");
+                  }
+                }
+              }
+            });
+      }
+      return new CovariantMethodsInJarResult(methodReferenceMap);
+    }
+
+    private static void createCovariantMethodReference(
+        MethodReference methodReference,
+        DexAnnotation covariantAnnotation,
+        Map<MethodReference, List<MethodReference>> methodReferenceMap) {
+      DexValueType newReturnType =
+          covariantAnnotation.annotation.getElement(0).getValue().asDexValueType();
+      methodReferenceMap
+          .computeIfAbsent(methodReference, ignoreKey(ArrayList::new))
+          .add(
+              Reference.method(
+                  methodReference.getHolderClass(),
+                  methodReference.getMethodName(),
+                  methodReference.getFormalTypes(),
+                  newReturnType.value.asClassReference()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index f11b7a1..ed2343a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -49,6 +49,17 @@
 
   public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
       ThrowableConsumer<T> setMockApiLevelForMethod(
+          MethodReference method, AndroidApiLevel apiLevel) {
+    return compilerBuilder -> {
+      compilerBuilder.addOptionsModification(
+          options -> {
+            options.apiModelingOptions().methodApiMapping.put(method, apiLevel);
+          });
+    };
+  }
+
+  public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
+      ThrowableConsumer<T> setMockApiLevelForMethod(
           Constructor<?> constructor, AndroidApiLevel apiLevel) {
     return compilerBuilder -> {
       compilerBuilder.addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/apimodel/JavaSourceCodePrinter.java b/src/test/java/com/android/tools/r8/apimodel/JavaSourceCodePrinter.java
new file mode 100644
index 0000000..fe1c78a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/JavaSourceCodePrinter.java
@@ -0,0 +1,264 @@
+// Copyright (c) 2022, 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.apimodel;
+
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public final class JavaSourceCodePrinter {
+
+  private final Set<String> imports = new HashSet<>();
+  private final String className;
+  private final String packageName;
+  private final String classModifiers;
+  private final String header;
+  private final StringBuilder bodyPrinter = new StringBuilder();
+
+  public enum KnownType {
+    Consumer("java.util.function", "Consumer", false),
+    DexItemFactory("com.android.tools.r8.graph", "DexItemFactory", false),
+    DexMethod("com.android.tools.r8.graph", "DexMethod", false);
+
+    private String packageName;
+    private String simpleName;
+    private boolean isPrimitive;
+
+    KnownType(String packageName, String simpleName, boolean isPrimitive) {
+      this.packageName = packageName;
+      this.simpleName = simpleName;
+      this.isPrimitive = isPrimitive;
+    }
+
+    boolean isPrimitive() {
+      return isPrimitive;
+    }
+
+    String getCanonicalName() {
+      return packageName + "." + simpleName;
+    }
+
+    String getSimpleName() {
+      return simpleName;
+    }
+  }
+
+  public JavaSourceCodePrinter(
+      String className, String packageName, String classModifiers, String header) {
+    this.className = className;
+    this.packageName = packageName;
+    this.classModifiers = classModifiers;
+    this.header = header;
+  }
+
+  public void addClassImport(KnownType type) {
+    if (!type.isPrimitive) {
+      imports.add(type.getCanonicalName());
+    }
+  }
+
+  public JavaSourceCodePrinter addMethod(
+      String modifiers,
+      Type returnType,
+      String name,
+      List<MethodParameter> parameters,
+      Consumer<JavaSourceCodeMethodPrinter> content) {
+    bodyPrinter.append(modifiers).append(" ");
+    if (returnType != null) {
+      bodyPrinter.append(returnType.toString(this::addClassImport));
+    } else {
+      bodyPrinter.append("void");
+    }
+    JavaSourceCodeMethodPrinter javaSourceCodeMethodPrinter = new JavaSourceCodeMethodPrinter();
+    content.accept(javaSourceCodeMethodPrinter);
+    bodyPrinter
+        .append(" ")
+        .append(name)
+        .append(
+            StringUtils.join(
+                ", ",
+                parameters,
+                parameter -> parameter.toString(this::addClassImport),
+                BraceType.PARENS))
+        .append(" {")
+        .append(StringUtils.LINE_SEPARATOR)
+        .append(javaSourceCodeMethodPrinter)
+        .append("}")
+        .append(StringUtils.LINE_SEPARATOR);
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder(header);
+    if (packageName != null) {
+      sb.append("package ").append(packageName).append(";").append(StringUtils.LINE_SEPARATOR);
+    }
+    sb.append(
+            StringUtils.joinLines(
+                imports.stream()
+                    .sorted()
+                    .map(imp -> "import " + imp + ";")
+                    .collect(Collectors.toList())))
+        .append(StringUtils.LINE_SEPARATOR);
+    return sb.append(StringUtils.LINE_SEPARATOR)
+        .append(classModifiers)
+        .append(" ")
+        .append("class ")
+        .append(className)
+        .append(" {")
+        .append(StringUtils.LINE_SEPARATOR)
+        .append(bodyPrinter)
+        .append("}")
+        .toString();
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private String className;
+    private String packageName = null;
+    private final String classModifiers = "public";
+    private String header = "";
+
+    public Builder setClassName(String className) {
+      this.className = className;
+      return this;
+    }
+
+    public Builder setPackageName(String packageName) {
+      this.packageName = packageName;
+      return this;
+    }
+
+    public Builder setHeader(String header) {
+      this.header = header;
+      return this;
+    }
+
+    public JavaSourceCodePrinter build() {
+      assert className != null;
+      return new JavaSourceCodePrinter(className, packageName, classModifiers, header);
+    }
+  }
+
+  public static class JavaSourceCodeMethodPrinter {
+
+    private final StringBuilder sb = new StringBuilder();
+
+    public JavaSourceCodeMethodPrinter addInstanceMethodCall(
+        String variable, String method, Action... content) {
+      return addInstanceMethodCall(variable, method, Arrays.asList(content));
+    }
+
+    public JavaSourceCodeMethodPrinter addInstanceMethodCall(
+        String variable, String method, Collection<Action> content) {
+      sb.append(variable).append(".").append(method).append("(");
+      boolean insertSeparator = false;
+      for (Action action : content) {
+        if (insertSeparator) {
+          sb.append(", ");
+        }
+        action.execute();
+        insertSeparator = true;
+      }
+      sb.append(")");
+      return this;
+    }
+
+    public Action literal(String constant) {
+      return () -> sb.append(StringUtils.quote(constant));
+    }
+
+    public JavaSourceCodeMethodPrinter addSemicolon() {
+      sb.append(";");
+      return this;
+    }
+
+    public JavaSourceCodeMethodPrinter newLine() {
+      sb.append(StringUtils.LINE_SEPARATOR);
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return sb.toString();
+    }
+  }
+
+  public static class Type {
+
+    private final KnownType type;
+
+    private Type(KnownType type) {
+      this.type = type;
+    }
+
+    public static Type fromType(KnownType type) {
+      return new Type(type);
+    }
+
+    public String toString(Consumer<KnownType> classConsumer) {
+      classConsumer.accept(type);
+      return type.getSimpleName();
+    }
+  }
+
+  public static class ParameterizedType extends Type {
+
+    private final Type[] arguments;
+
+    private ParameterizedType(KnownType clazz, Type[] arguments) {
+      super(clazz);
+      this.arguments = arguments;
+    }
+
+    public static ParameterizedType fromType(KnownType type, Type... arguments) {
+      return new ParameterizedType(type, arguments);
+    }
+
+    @Override
+    public String toString(Consumer<KnownType> classConsumer) {
+      StringBuilder sb = new StringBuilder();
+      sb.append(super.toString(classConsumer));
+      if (arguments.length == 0) {
+        return sb.toString();
+      }
+      sb.append("<");
+      sb.append(
+          StringUtils.join(", ", Arrays.asList(arguments), type -> type.toString(classConsumer)));
+      sb.append(">");
+      return sb.toString();
+    }
+  }
+
+  public static class MethodParameter {
+
+    private final Type type;
+    private final String name;
+
+    private MethodParameter(Type type, String name) {
+      this.type = type;
+      this.name = name;
+    }
+
+    public static MethodParameter build(Type type, String name) {
+      return new MethodParameter(type, name);
+    }
+
+    private String toString(Consumer<KnownType> classConsumer) {
+      return type.toString(classConsumer) + " " + name;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
index a8f662f..5c30550 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
@@ -6,6 +6,7 @@
 import static java.util.Collections.emptyList;
 
 import com.android.tools.r8.benchmarks.appdumps.TiviBenchmarks;
+import com.android.tools.r8.benchmarks.desugaredlib.L8Benchmark;
 import com.android.tools.r8.benchmarks.desugaredlib.LegacyDesugaredLibraryBenchmark;
 import com.android.tools.r8.benchmarks.helloworld.HelloWorldBenchmark;
 import com.android.tools.r8.benchmarks.retrace.RetraceStackTraceBenchmark;
@@ -47,6 +48,7 @@
     // Every benchmark that should be active on golem must be setup in this method.
     HelloWorldBenchmark.configs().forEach(collection::addBenchmark);
     LegacyDesugaredLibraryBenchmark.configs().forEach(collection::addBenchmark);
+    L8Benchmark.configs().forEach(collection::addBenchmark);
     TiviBenchmarks.configs().forEach(collection::addBenchmark);
     RetraceStackTraceBenchmark.configs().forEach(collection::addBenchmark);
     return collection;
diff --git a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
new file mode 100644
index 0000000..147d601
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2022, 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.benchmarks.desugaredlib;
+
+import com.android.tools.r8.L8TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestState;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.android.tools.r8.benchmarks.BenchmarkDependency;
+import com.android.tools.r8.benchmarks.BenchmarkEnvironment;
+import com.android.tools.r8.benchmarks.BenchmarkTarget;
+import com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class L8Benchmark extends BenchmarkBase {
+
+  private static final BenchmarkDependency androidJar = BenchmarkDependency.getAndroidJar30();
+  private static final BenchmarkDependency jdk11Conf =
+      new BenchmarkDependency(
+          "legacyConf", "desugar_jdk_libs_11", Paths.get("third_party", "openjdk"));
+
+  public L8Benchmark(BenchmarkConfig config, TestParameters parameters) {
+    super(config, parameters);
+  }
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return parametersFromConfigs(configs());
+  }
+
+  public static List<BenchmarkConfig> configs() {
+    return ImmutableList.of(
+        BenchmarkConfig.builder()
+            .setName("L8Benchmark")
+            .setTarget(BenchmarkTarget.D8)
+            .setFromRevision(12733)
+            .setMethod(L8Benchmark::run)
+            .addDependency(androidJar)
+            .addDependency(jdk11Conf)
+            .measureRunTime()
+            .build());
+  }
+
+  public static void run(BenchmarkEnvironment environment) throws Exception {
+    Path undesugarJdkLib =
+        DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
+            environment.getTemp().newFolder("undesugar_jdk_lib").toPath(),
+            jdk11Conf.getRoot(environment).resolve("desugar_jdk_libs.jar"));
+    LibraryDesugaringSpecification spec =
+        new LibraryDesugaringSpecification(
+            "JDK11_Benchmark",
+            ImmutableSet.of(undesugarJdkLib),
+            Paths.get("src/library_desugar/jdk11/desugar_jdk_libs.json"),
+            ImmutableSet.of(androidJar.getRoot(environment).resolve("android.jar")),
+            "");
+    runner(environment.getConfig())
+        .setWarmupIterations(1)
+        .setBenchmarkIterations(10)
+        .reportResultSum()
+        .run(
+            results -> {
+              long start = System.nanoTime();
+              L8TestBuilder.create(
+                      AndroidApiLevel.B, Backend.DEX, new TestState(environment.getTemp()))
+                  .apply(spec::configureL8TestBuilder)
+                  .compile();
+              long end = System.nanoTime();
+              results.addRuntimeResult(end - start);
+            });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/PackageInfoClassFileWithoutAbstractAccessModifierTest.java b/src/test/java/com/android/tools/r8/cf/PackageInfoClassFileWithoutAbstractAccessModifierTest.java
new file mode 100644
index 0000000..0db0bbf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/PackageInfoClassFileWithoutAbstractAccessModifierTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2022, 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.cf;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.Matchers;
+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;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PackageInfoClassFileWithoutAbstractAccessModifierTest extends TestBase
+    implements Opcodes {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public Backend backend;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
+  }
+
+  private static final String CLASS_NAME = "it.unimi.dsi.fastutil.package-info";
+
+  private void inspect(CodeInspector inspector, boolean isR8) {
+    ClassSubject packageInfo = inspector.clazz(CLASS_NAME);
+    assertThat(packageInfo, Matchers.isInterface());
+    assertEquals(backend.isDex() || isR8, packageInfo.isAbstract());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(backend)
+        .addProgramClassFileData(dump())
+        .setMinApi(AndroidApiLevel.B)
+        .compile()
+        .inspect(inspector -> inspect(inspector, false));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(backend)
+        .addProgramClassFileData(dump())
+        .addKeepMainRule(CLASS_NAME)
+        .setMinApi(AndroidApiLevel.B)
+        .compile()
+        .inspect(inspector -> inspect(inspector, true));
+  }
+
+  // Some versions of javac would generate package-info class files without ACC_ABSTRACT.
+  // Dump of it/unimi/dsi/fastutil/package-info.class from fastutil-8.5.8.jar.
+  public static byte[] dump() throws Exception {
+    ClassWriter classWriter = new ClassWriter(0);
+    classWriter.visit(
+        V1_5, ACC_INTERFACE, "it/unimi/dsi/fastutil/package-info", null, "java/lang/Object", null);
+    classWriter.visitSource("package-info.java", null);
+    classWriter.visitEnd();
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 2c58d15..692773c 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -52,16 +52,20 @@
 
   public String getHeaderString() {
     String simpleName = getClass().getSimpleName();
+    return getHeaderString(getYear(), simpleName)
+        + StringUtils.lines("package " + getGeneratedClassPackageName() + ";");
+  }
+
+  public static String getHeaderString(int year, String simpeNameOfGenerator) {
     return StringUtils.lines(
-        "// Copyright (c) " + getYear() + ", the R8 project authors. Please see the AUTHORS file",
+        "// Copyright (c) " + year + ", the R8 project authors. Please see the AUTHORS file",
         "// for details. All rights reserved. Use of this source code is governed by a",
         "// BSD-style license that can be found in the LICENSE file.",
         "",
         "// ***********************************************************************************",
-        "// GENERATED FILE. DO NOT EDIT! See " + simpleName + ".java.",
+        "// GENERATED FILE. DO NOT EDIT! See " + simpeNameOfGenerator + ".java.",
         "// ***********************************************************************************",
-        "",
-        "package " + getGeneratedClassPackageName() + ";");
+        "");
   }
 
   protected Path getGeneratedFile() {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
index f8e88c6..6891000 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
@@ -10,17 +10,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.transformers.MethodTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.Label;
 
 @RunWith(Parameterized.class)
 public class NoLineInfoTest extends TestBase {
@@ -47,21 +42,7 @@
   private byte[] getTestClassTransformed() throws IOException {
     return transformer(TestClass.class)
         .setSourceFile(INPUT_SOURCE_FILE)
-        .addMethodTransformer(
-            new MethodTransformer() {
-              private final Map<MethodReference, Integer> lines = new HashMap<>();
-
-              @Override
-              public void visitLineNumber(int line, Label start) {
-                Integer nextLine = lines.getOrDefault(getContext().getReference(), 0);
-                if (nextLine > 0) {
-                  super.visitLineNumber(nextLine, start);
-                }
-                // Increment the actual line content by 100 so that each one is clearly distinct
-                // from a PC value for any of the methods.
-                lines.put(getContext().getReference(), nextLine + 100);
-              }
-            })
+        .setPredictiveLineNumbering()
         .transform();
   }
 
@@ -145,10 +126,27 @@
 
   // Residual line depends on the source file parameter.
   private StackTraceLine residualLine(String method, int line) {
-    String defaultFile =
-        isCompileWithPcAsLineNumberSupport() ? UNKNOWN_SOURCE_FILE : DEFAULT_SOURCE_FILE;
-    String file = customSourceFile ? CUSTOM_SOURCE_FILE : defaultFile;
-    return line(file, method, line);
+    if (customSourceFile) {
+      return line(CUSTOM_SOURCE_FILE, method, line);
+    }
+    if (isCompileWithPcAsLineNumberSupport()) {
+      return line(UNKNOWN_SOURCE_FILE, method, line);
+    }
+    return line(DEFAULT_SOURCE_FILE, method, line);
+  }
+
+  // A residual line that is either null debug info or pc2pc mapping.
+  private StackTraceLine residualPcOrNoLine(String method, int pc) {
+    // If compiling with custom source file the debug info must be non-null and have a pc mapping.
+    if (customSourceFile) {
+      return line(CUSTOM_SOURCE_FILE, method, pc);
+    }
+    // If debug info is null, then on native pc support VMs it will print "unknown" and pc.
+    if (isRuntimeWithPcAsLineNumberSupport()) {
+      return line(UNKNOWN_SOURCE_FILE, method, pc);
+    }
+    // On old runtimes it will print "default" and no line info.
+    return line(DEFAULT_SOURCE_FILE, method, -1);
   }
 
   // This is the real "reference" stack trace as given by JVM on inputs and should be retraced to.
@@ -174,10 +172,18 @@
 
   // TODO(b/232212653): The retraced stack trace should be the same as `getExpectedInputStacktrace`.
   private StackTrace getUnexpectedRetracedStacktrace() {
-
-    // TODO(b/232212653): Retracing the PC 1 preserves it but it should map to <noline>
-    StackTraceLine fooLine =
-        isRuntimeWithPcAsLineNumberSupport() ? inputLine("foo", 1) : inputLine("foo", -1);
+    StackTraceLine fooLine;
+    if (parameters.isCfRuntime()) {
+      fooLine = inputLine("foo", -1);
+    } else if (customSourceFile) {
+      // TODO(b/232212653): Should retrace convert out of "0" and represent it as <noline>/-1?
+      fooLine = inputLine("foo", 0);
+    } else if (isRuntimeWithPcAsLineNumberSupport()) {
+      // TODO(b/232212653): Retrace should convert PC 1 to line <noline>/-1/0.
+      fooLine = inputLine("foo", 1);
+    } else {
+      fooLine = inputLine("foo", -1);
+    }
 
     // TODO(b/232212653): Normal line-opt will cause a single-line mapping. Retrace should not
     //  optimize that to mean it represents a single possible line. (<noline> should not match 1:x).
@@ -207,17 +213,9 @@
           .build();
     }
 
-    // TODO(b/232212653): The correct line should be with CUSTOM_SOURCE_FILE and PC 1.
-    //   When compiling with debug info encoding PCs this is almost the expected output. The issue
-    //   being that even "foo" should have PC based encoding too to ensure the SF remains on
-    //   newer VMs too.
-    StackTraceLine fooLine =
-        isRuntimeWithPcAsLineNumberSupport()
-            ? line(UNKNOWN_SOURCE_FILE, "foo", 1)
-            : residualLine("foo", -1);
-
     return StackTrace.builder()
-        .add(fooLine)
+        // Foo has only <noline> on input and so it is allowed to compile it to a null debug-info.
+        .add(residualPcOrNoLine("foo", 1))
         .add(residualLine("bar", 0))
         .add(residualLine("baz", 0))
         .add(residualLine("main", 6))
diff --git a/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java b/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java
index d786a12..596707f 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java
@@ -7,8 +7,9 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -21,8 +22,8 @@
   @Parameter() public TestParameters parameters;
 
   @Parameters(name = "{0}")
-  public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build();
   }
 
   @Test
@@ -32,17 +33,15 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
-            // TODO(b/233857593): There used to be only one goto.
-            inspector -> {
-              assertEquals(
-                  2,
-                  inspector
-                      .clazz(TestClass.class)
-                      .uniqueMethodWithName("testLoopPhiWithNullFirstInput")
-                      .streamInstructions()
-                      .filter(InstructionSubject::isGoto)
-                      .count());
-            });
+            inspector ->
+                assertEquals(
+                    1,
+                    inspector
+                        .clazz(TestClass.class)
+                        .uniqueMethodWithName("testLoopPhiWithNullFirstInput")
+                        .streamInstructions()
+                        .filter(InstructionSubject::isGoto)
+                        .count()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
index 1834ea4..d31aa91 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
@@ -5,13 +5,13 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
-import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestBuilder;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
@@ -43,7 +43,7 @@
     LibraryDesugaringSpecification jdk11InvalidLib =
         new LibraryDesugaringSpecification(
             "JDK11_INVALID_LIB",
-            UNDESUGARED_JDK_11_LIB_JAR,
+            ToolHelper.getUndesugaredJdk11LibJarForTesting(),
             "jdk11/desugar_jdk_libs.json",
             AndroidApiLevel.L);
     return buildParameters(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
index 91fa3d5..1f4d9c1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
@@ -20,9 +20,7 @@
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.Box;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.List;
@@ -147,7 +145,8 @@
             .addProgramClasses(Library.class)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(
-                LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
+                LibraryDesugaringTestConfiguration.forSpecification(
+                    libraryDesugaringSpecification.getSpecification()))
             .compile()
             .writeToZip();
 
@@ -159,7 +158,8 @@
           .setMinApi(parameters.getApiLevel())
           .compileWithExpectedDiagnostics(
               diagnostics -> {
-                if (requiresAnyCoreLibDesugaring(parameters.getApiLevel())) {
+                if (requiresAnyCoreLibDesugaring(
+                    parameters.getApiLevel(), libraryDesugaringSpecification != JDK8)) {
                   diagnostics.assertOnlyErrors();
                   diagnostics.assertErrorsMatch(
                       diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
@@ -213,14 +213,12 @@
   public void testMergeDifferentLibraryDesugarVersions() throws Exception {
     // DEX code with library desugaring using a desugared library configuration with a
     // different identifier.
-    Box<LegacyDesugaredLibrarySpecification> box = new Box<>();
     Path libraryDex =
         testForD8(Backend.DEX)
             .addProgramClasses(Library.class)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(
                 LibraryDesugaringTestConfiguration.builder()
-                    .setMinApi(parameters.getApiLevel())
                     // Minimal configuration with a different identifier.
                     // The j$.time is rewritten because empty flags are equivalent to an empty
                     // specification, and no marker is set for empty specifications.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index b833d04..4c05352 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestState;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestBuilder;
@@ -23,28 +22,9 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import org.junit.BeforeClass;
 
 public class DesugaredLibraryTestBase extends TestBase {
 
-  private static final boolean FORCE_JDK11_DESUGARED_LIB = false;
-
-  @BeforeClass
-  public static void setUpDesugaredLibrary() {
-    if (!FORCE_JDK11_DESUGARED_LIB) {
-      return;
-    }
-    System.setProperty("desugar_jdk_json_dir", "src/library_desugar/jdk11");
-    System.setProperty(
-        "desugar_jdk_libs", "third_party/openjdk/desugar_jdk_libs_11/desugar_jdk_libs.jar");
-    System.out.println("Forcing the usage of JDK11 desugared library.");
-  }
-
-  public static boolean isJDK11DesugaredLibrary() {
-    String property = System.getProperty("desugar_jdk_json_dir", "");
-    return property.contains("jdk11");
-  }
-
   // For conversions tests, we need DexRuntimes where classes to convert are present (DexRuntimes
   // above N and O depending if Stream or Time APIs are used), but we need to compile the program
   // with a minAPI below to force the use of conversions.
@@ -65,14 +45,6 @@
     throw new Error("Unsupported conversion parameters");
   }
 
-  protected AndroidApiLevel getRequiredCompilationAPILevel() {
-    return isJDK11DesugaredLibrary() ? AndroidApiLevel.R : AndroidApiLevel.P;
-  }
-
-  protected Path getLibraryFile() {
-    return ToolHelper.getAndroidJar(getRequiredCompilationAPILevel());
-  }
-
   protected boolean requiresEmulatedInterfaceCoreLibDesugaring(TestParameters parameters) {
     return parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
   }
@@ -82,23 +54,11 @@
         < (isJDK11 ? AndroidApiLevel.S.getLevel() : AndroidApiLevel.O.getLevel());
   }
 
-  protected boolean requiresTimeDesugaring(TestParameters parameters) {
-    return requiresTimeDesugaring(parameters, isJDK11DesugaredLibrary());
-  }
-
-  protected boolean requiresAnyCoreLibDesugaring(TestParameters parameters) {
-    return requiresAnyCoreLibDesugaring(parameters.getApiLevel());
-  }
-
   protected boolean requiresAnyCoreLibDesugaring(AndroidApiLevel apiLevel, boolean isJDK11) {
     return apiLevel.getLevel()
         <= (isJDK11 ? AndroidApiLevel.R.getLevel() : AndroidApiLevel.N_MR1.getLevel());
   }
 
-  protected boolean requiresAnyCoreLibDesugaring(AndroidApiLevel apiLevel) {
-    return requiresAnyCoreLibDesugaring(apiLevel, isJDK11DesugaredLibrary());
-  }
-
   protected DesugaredLibraryTestBuilder<?> testForDesugaredLibrary(
       TestParameters parameters,
       LibraryDesugaringSpecification libraryDesugaringSpecification,
@@ -126,6 +86,26 @@
     }
   }
 
+  public Path getNonShrunkDesugaredLib(
+      TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification)
+      throws Exception {
+    return testForL8(parameters.getApiLevel(), parameters.getBackend())
+        .apply(libraryDesugaringSpecification::configureL8TestBuilder)
+        .compile()
+        .writeToZip();
+  }
+
+  public Path getNonShrunkDesugaredLib(
+      AndroidApiLevel apiLevel,
+      Backend backend,
+      LibraryDesugaringSpecification libraryDesugaringSpecification)
+      throws Exception {
+    return testForL8(apiLevel, backend)
+        .apply(libraryDesugaringSpecification::configureL8TestBuilder)
+        .compile()
+        .writeToZip();
+  }
+
   public static Path[] getAllFilesWithSuffixInDirectory(Path directory, String suffix)
       throws IOException {
     return Files.walk(directory)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java
index 18d1613..dd036e2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static junit.framework.TestCase.assertTrue;
 
@@ -151,7 +152,7 @@
   @Test
   public void testJ$ExtensionDesugaring() throws Throwable {
     Assume.assumeFalse(parameters.isCfRuntime());
-    Assume.assumeTrue(requiresTimeDesugaring(parameters));
+    Assume.assumeTrue(requiresTimeDesugaring(parameters, libraryDesugaringSpecification != JDK8));
     String stdErr =
         testForDesugaredLibrary(
                 parameters, libraryDesugaringSpecification, compilationSpecification)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java
index 944036e..8d06a2f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.RELEASED_1_1_5;
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
@@ -15,7 +16,6 @@
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -37,7 +37,6 @@
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,13 +44,18 @@
 @RunWith(Parameterized.class)
 public class LegacyDesugaredLibraryConfigurationParsingTest extends DesugaredLibraryTestBase {
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameterized.Parameters(name = "{0}, spec: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withNoneRuntime().build(), ImmutableList.of(RELEASED_1_1_5));
   }
 
-  public LegacyDesugaredLibraryConfigurationParsingTest(TestParameters parameters) {
+  public LegacyDesugaredLibraryConfigurationParsingTest(
+      TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
     parameters.assertNoneRuntime();
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
   }
 
   final AndroidApiLevel minApi = AndroidApiLevel.B;
@@ -87,7 +91,6 @@
   }
 
   private LegacyDesugaredLibrarySpecificationParser parser(DiagnosticsHandler handler) {
-    Assume.assumeFalse(isJDK11DesugaredLibrary());
     return new LegacyDesugaredLibrarySpecificationParser(
         factory, new Reporter(handler), libraryCompilation, minApi.getLevel());
   }
@@ -117,9 +120,7 @@
   public void testReference() throws Exception {
     // Just test that the reference file parses without issues.
     LegacyDesugaredLibrarySpecification spec =
-        runPassing(
-            StringResource.fromFile(
-                LibraryDesugaringSpecification.RELEASED_1_1_5.getSpecification()));
+        runPassing(StringResource.fromFile(libraryDesugaringSpecification.getSpecification()));
     assertEquals(libraryCompilation, spec.isLibraryCompilation());
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index ddf8c9d..3c4a223 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -128,7 +128,7 @@
     Path jdkLibJar =
         libraryDesugaringSpecification == JDK8
             ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
-            : ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
+            : ToolHelper.getUndesugaredJdk11LibJarForTesting();
     GenerateLintFiles.main(
         new String[] {
           libraryDesugaringSpecification.getSpecification().toString(),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java
index 23a146e..3d1a75b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexField;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -52,7 +54,7 @@
   @Test
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.R))
         .addProgramClasses(DesugarLocalDate.class)
         .addProgramClassFileData(getMainClassFileData())
         .setMinApi(parameters.getApiLevel())
@@ -66,7 +68,7 @@
   public void testR8() throws Exception {
     Assume.assumeTrue(parameters.isDexRuntime());
     testForR8(parameters.getBackend())
-        .addLibraryFiles(getLibraryFile())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.R))
         .addProgramClasses(DesugarLocalDate.class)
         .addProgramClassFileData(getMainClassFileData())
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index cfb5b67..ca676d3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -11,6 +11,8 @@
 import static com.android.tools.r8.MarkerMatcher.markerIsDesugared;
 import static com.android.tools.r8.MarkerMatcher.markerMinApi;
 import static com.android.tools.r8.MarkerMatcher.markerTool;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
@@ -25,7 +27,7 @@
 import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -37,6 +39,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class MergingWithDesugaredLibraryTest extends DesugaredLibraryTestBase {
@@ -45,14 +48,18 @@
   private static final String J$_RESULT = "j$.util.stream.ReferencePipeline$Head";
 
   private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{0}, spec: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), getJdk8Jdk11());
   }
 
-  public MergingWithDesugaredLibraryTest(TestParameters parameters) {
+  public MergingWithDesugaredLibraryTest(
+      TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
   }
 
   @Test
@@ -61,15 +68,18 @@
     try {
       compileResult =
           testForD8()
-              .addLibraryFiles(getLibraryFile())
+              .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
               .addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
               .setMinApi(parameters.getApiLevel())
               .applyIf(
                   someLibraryDesugaringRequired(),
                   b ->
                       b.enableCoreLibraryDesugaring(
-                          LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
-              .compileWithExpectedDiagnostics(this::assertError);
+                          LibraryDesugaringTestConfiguration.forSpecification(
+                              libraryDesugaringSpecification.getSpecification())))
+              .compileWithExpectedDiagnostics(this::assertError)
+              .addRunClasspathFiles(
+                  getNonShrunkDesugaredLib(parameters, libraryDesugaringSpecification));
       assertFalse(expectError());
     } catch (CompilationFailedException e) {
       assertTrue(expectError());
@@ -108,13 +118,14 @@
     Path app =
         testForD8()
             .addProgramFiles(buildPart1DesugaredLibrary(), shrunkenLib)
-            .addLibraryFiles(getLibraryFile())
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
             .setMinApi(parameters.getApiLevel())
             .applyIf(
                 someLibraryDesugaringRequired(),
                 b ->
                     b.enableCoreLibraryDesugaring(
-                        LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
+                        LibraryDesugaringTestConfiguration.forSpecification(
+                            libraryDesugaringSpecification.getSpecification())))
             .compile()
             .writeToZip();
 
@@ -123,7 +134,9 @@
         allOf(
             markerTool(Tool.D8),
             markerIsDesugared(),
-            markerHasDesugaredLibraryIdentifier(requiresAnyCoreLibDesugaring(parameters)));
+            markerHasDesugaredLibraryIdentifier(
+                requiresAnyCoreLibDesugaring(
+                    parameters.getApiLevel(), libraryDesugaringSpecification != JDK8)));
     assertMarkersMatch(
         ExtractMarker.extractMarkerFromDexFile(app), ImmutableList.of(libraryMatcher, d8Matcher));
   }
@@ -209,7 +222,8 @@
   }
 
   private boolean someLibraryDesugaringRequired() {
-    return requiresAnyCoreLibDesugaring(parameters);
+    return requiresAnyCoreLibDesugaring(
+        parameters.getApiLevel(), libraryDesugaringSpecification != JDK8);
   }
 
   @Test
@@ -218,14 +232,17 @@
         testForD8()
             .addProgramFiles(buildPart1DesugaredLibrary())
             .addProgramClasses(Part2.class)
-            .addLibraryFiles(getLibraryFile())
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
             .setMinApi(parameters.getApiLevel())
             .applyIf(
                 someLibraryDesugaringRequired(),
                 b ->
                     b.enableCoreLibraryDesugaring(
-                        LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
+                        LibraryDesugaringTestConfiguration.forSpecification(
+                            libraryDesugaringSpecification.getSpecification())))
             .compile()
+            .addRunClasspathFiles(
+                getNonShrunkDesugaredLib(parameters, libraryDesugaringSpecification))
             .inspectDiagnosticMessages(this::assertWarningPresent);
     if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
       compileResult
@@ -256,14 +273,15 @@
 
   private Path buildPart1DesugaredLibrary() throws Exception {
     return testForD8()
-        .addLibraryFiles(getLibraryFile())
+        .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
         .addProgramClasses(Part1.class)
         .setMinApi(parameters.getApiLevel())
         .applyIf(
             someLibraryDesugaringRequired(),
             b ->
                 b.enableCoreLibraryDesugaring(
-                    LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
+                    LibraryDesugaringTestConfiguration.forSpecification(
+                        libraryDesugaringSpecification.getSpecification())))
         .compile()
         .writeToZip();
   }
@@ -278,6 +296,7 @@
 
   @SuppressWarnings("RedundantOperationOnEmptyContainer")
   static class Part1 {
+
     public static void main(String[] args) {
       System.out.println(new ArrayList<>().stream().getClass().getName());
     }
@@ -285,6 +304,7 @@
 
   @SuppressWarnings("RedundantOperationOnEmptyContainer")
   static class Part2 {
+
     public static void main(String[] args) {
       System.out.println(new ArrayList<>().stream().getClass().getName());
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
index 82ca473..9e8583f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
@@ -12,7 +12,6 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.smali.SmaliBuilder;
@@ -85,9 +84,7 @@
     try {
       Path input =
           testForL8(parameters.getApiLevel())
-              .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
-              .setDebug()
-              .setDesugarJDKLibsCustomConversions(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+              .apply(libraryDesugaringSpecification::configureL8TestBuilder)
               .compile()
               .writeToZip();
       testForD8()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index f503773..f5717d8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
-import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
@@ -17,6 +16,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -76,7 +76,7 @@
     LibraryDesugaringSpecification jdk11MaxCompileSdk =
         new LibraryDesugaringSpecification(
             "JDK11_MAX",
-            UNDESUGARED_JDK_11_LIB_JAR,
+            ToolHelper.getUndesugaredJdk11LibJarForTesting(),
             "jdk11/desugar_jdk_libs.json",
             AndroidApiLevel.LATEST);
     return buildParameters(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 8ed7907..492964e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
-import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
@@ -62,7 +61,7 @@
         new LibraryDesugaringSpecification(
             "JDK11_CL",
             ImmutableSet.of(
-                UNDESUGARED_JDK_11_LIB_JAR,
+                ToolHelper.getUndesugaredJdk11LibJarForTesting(),
                 ToolHelper.DESUGAR_LIB_CONVERSIONS,
                 ToolHelper.getCoreLambdaStubs()),
             JDK11.getSpecification(),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
index 0901073..7930dbe 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
@@ -3,10 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
 import com.android.tools.r8.LibraryDesugaringTestConfiguration;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import java.lang.reflect.InvocationTargetException;
@@ -27,17 +29,22 @@
 
 // For context see b/229793269.
 @RunWith(Parameterized.class)
-public class PseudoPlatformApiTest extends TestBase {
+public class PseudoPlatformApiTest extends DesugaredLibraryTestBase {
 
-  @Parameter() public TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameters(name = "{0}")
+  @Parameter(1)
+  public LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, spec: {1}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters()
             .withDexRuntimes()
             .withApiLevelsStartingAtIncluding(AndroidApiLevel.P)
-            .build());
+            .build(),
+        getJdk8Jdk11());
   }
 
   private Path androidJarAdditions() throws Exception {
@@ -104,8 +111,12 @@
         .addProgramClasses(ProgramClass.class)
         .setMinApi(AndroidApiLevel.H_MR2)
         .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+            LibraryDesugaringTestConfiguration.forSpecification(
+                libraryDesugaringSpecification.getSpecification()))
         .addRunClasspathFiles(androidJarAdditionsDex())
+        .addRunClasspathFiles(
+            getNonShrunkDesugaredLib(
+                AndroidApiLevel.H_MR2, parameters.getBackend(), libraryDesugaringSpecification))
         .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutputLines("DEFAULT-X", "Y-DEFAULT");
   }
@@ -119,9 +130,13 @@
         .addProgramClasses(ProgramClass.class)
         .setMinApi(AndroidApiLevel.H_MR2)
         .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+            LibraryDesugaringTestConfiguration.forSpecification(
+                libraryDesugaringSpecification.getSpecification()))
         .addRunClasspathFiles(androidJarAdditionsDex())
         .addRunClasspathFiles(oemDex())
+        .addRunClasspathFiles(
+            getNonShrunkDesugaredLib(
+                AndroidApiLevel.H_MR2, parameters.getBackend(), libraryDesugaringSpecification))
         .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutputLines("OEM-X", "Y-OEM");
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
index 6d0d72f..91721e2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
@@ -8,8 +8,8 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyTopLevelFlags;
@@ -31,15 +31,19 @@
 public class RetargetAndBackportTest extends DesugaredLibraryTestBase implements Opcodes {
 
   private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
 
-  @Parameters(name = "{0} {1}")
+  @Parameters(name = "{0}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withDexRuntime(Version.DEFAULT).withCfRuntime(CfVm.JDK11).build());
+        getTestParameters().withDexRuntime(Version.DEFAULT).withCfRuntime(CfVm.JDK11).build(),
+        LibraryDesugaringSpecification.getJdk8Jdk11());
   }
 
-  public RetargetAndBackportTest(TestParameters parameters) {
+  public RetargetAndBackportTest(
+      TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
     this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
   }
 
   /**
@@ -65,9 +69,9 @@
   @Test
   public void test() throws Exception {
     testForL8(AndroidApiLevel.B, parameters.getBackend())
-        .noDefaultDesugarJDKLibs()
         .addProgramClassFileData(dump())
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()))
+        .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+        .setDesugaredLibrarySpecification(libraryDesugaringSpecification.getSpecification())
         .addOptionsModifier(RetargetAndBackportTest::specifyDesugaredLibrary)
         .compile()
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
index ed4b818..985e6d4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
@@ -67,7 +67,8 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(cls)
         .enableCoreLibraryDesugaring(
-            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
+            LibraryDesugaringTestConfiguration.forSpecification(
+                libraryDesugaringSpecification.getSpecification()))
         .compile()
         .writeToZip();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java
index 24169b1..69b83bf 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
@@ -64,7 +65,7 @@
       assertTrue(inspector.clazz("j$.util.LongSummaryStatisticsConversions").isPresent());
       assertTrue(inspector.clazz("j$.util.IntSummaryStatisticsConversions").isPresent());
       assertTrue(inspector.clazz("j$.util.DoubleSummaryStatisticsConversions").isPresent());
-    } else if (requiresTimeDesugaring(parameters)) {
+    } else if (requiresTimeDesugaring(parameters, libraryDesugaringSpecification != JDK8)) {
       assertEquals(1, conversionsClasses.size());
       assertTrue(inspector.clazz("j$.time.TimeConversions").isPresent());
     } else {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
index b5175a1..e2ecec4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
 
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.MethodTransformer;
@@ -16,7 +15,6 @@
 import java.nio.file.Files;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
 import java.util.Map;
 import java.util.zip.ZipEntry;
@@ -39,22 +37,10 @@
           .put("wrapper/adapter/HybridFileTypeDetector", "java/adapter/HybridFileTypeDetector")
           .build();
 
-  public static void main(String[] args) throws Exception {
-    setUpDesugaredLibrary();
-    undesugaredJar();
-  }
-
-  public static Path undesugaredJar() {
-    if (!isJDK11DesugaredLibrary()) {
-      return ToolHelper.getDesugarJDKLibsBazelGeneratedFile();
-    }
-    return undesugaredJarJDK11(ToolHelper.getDesugarJDKLibsBazelGeneratedFile());
-  }
-
-  public static Path undesugaredJarJDK11(Path jdk11Jar) {
-    String string = jdk11Jar.toString();
-    Path desugaredLibJDK11Undesugared =
-        Paths.get(string.substring(0, string.length() - 4) + "_undesugared.jar");
+  public static Path undesugaredJarJDK11(Path undesugarFolder, Path jdk11Jar) {
+    String fileName = jdk11Jar.getFileName().toString();
+    String newFileName = fileName.substring(0, fileName.length() - 4) + "_undesugared.jar";
+    Path desugaredLibJDK11Undesugared = undesugarFolder.resolve(newFileName);
     return generateUndesugaredJar(jdk11Jar, desugaredLibJDK11Undesugared);
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
index f586605..7d09ee0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
 
 import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getPathsFiles;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getPathsFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
index 0c4e38a..a3641d6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
 
 import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getPathsFiles;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getPathsFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
new file mode 100644
index 0000000..5cb8e57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
@@ -0,0 +1,325 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary.jdktests;
+
+import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.EXTENSION_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.JDK11_PATH_JAVA_BASE_EXT;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestCompileResult;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class Jdk11NioFileTests extends DesugaredLibraryTestBase {
+
+  private static final Path JDK_11_NIO_TEST_FILES_DIR =
+      Paths.get(ToolHelper.JDK_11_TESTS_DIR).resolve("java/nio/file");
+  private static Path TEST_UTIL_JAR;
+  private static List<byte[]> TEST_PROGRAM_CLASS_DATA;
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() throws Exception {
+    List<LibraryDesugaringSpecification> specs;
+    if (ToolHelper.isWindows()) {
+      // The library configuration is not available on windows. Do not run anything.
+      specs = ImmutableList.of();
+    } else {
+      Jdk11TestLibraryDesugaringSpecification.setUp();
+      specs = ImmutableList.of(JDK11_PATH_JAVA_BASE_EXT);
+    }
+    return buildParameters(
+        // TODO(134732760): Support Dalvik VMs, currently fails because libjavacrypto is required
+        // and present only in ART runtimes.
+        getTestParameters()
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        specs,
+        ImmutableList.of(D8_L8DEBUG, D8_L8SHRINK));
+  }
+
+  public Jdk11NioFileTests(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  private static final List<String> EXCLUDE_COMPILATION =
+      ImmutableList.copyOf(
+          new String[] {
+            // We cannot run these tests due to missing dependencies.
+            "java/nio/file/FileStore/Basic.java",
+            "java/nio/file/Path/MacPathTest.java",
+            "java/nio/file/WatchService/LotsOfEvents.java",
+            "java/nio/file/Files/StreamLinesTest.java",
+            "java/nio/file/Files/walkFileTree/FindTest.java",
+            "java/nio/file/Files/DeleteOnClose.java",
+            "java/nio/file/Files/CopyAndMove.java",
+            "java/nio/file/FileSystem/Basic.java",
+            // Skip module info not used on Android.
+            "module-info.java"
+          });
+
+  // We distinguish 2 kinds of tests:
+  // - Main tests, which are run by running the main method, and succeed if no error is raised.
+  // - TestNG tests, which are run using testNG.
+  private static final List<String> SUCCESSFUL_MAIN_TESTS =
+      ImmutableList.of(
+          "PathUriImportExport",
+          "PathMisc",
+          "probeContentTypeParallelProbes",
+          "probeContentTypeForceLoad",
+          "AclEntryEmptySet",
+          "DirectoryStreamBasic",
+          "DirectoryStreamSecureDS",
+          "DirectoryStreamDriveLetter",
+          "FileTimeBasic",
+          "BasicFileAttributeViewCreationTime",
+          "BasicFileAttributeViewBasic",
+          "etcExceptions",
+          "walkFileTreeTerminateWalk",
+          "walkFileTreeNulls",
+          "walkFileTreeCreateFileTree",
+          "walkFileTreeMaxDepth",
+          "walkFileTreeSkipSiblings",
+          "walkFileTreeSkipSubtree",
+          "FilesSBC",
+          "FilesNameLimits",
+          "FilesCustomOptions",
+          "FilesLinks",
+          "WatchServiceBasic",
+          "WatchServiceFileTreeModifier",
+          "WatchServiceDeleteInterference",
+          "WatchServiceMayFlies",
+          "WatchServiceLotsOfCancels",
+          "WatchServiceSensitivityModifier");
+  private static final List<String> FAILING_MAIN_TESTS =
+      ImmutableList.of(
+          "PathPathOps",
+          "PathMacPath",
+          "DosFileAttributeViewBasic",
+          "probeContentTypeBasic",
+          "AclFileAttributeViewBasic",
+          "UserDefinedFileAttributeViewBasic",
+          "PosixFileAttributeViewBasic",
+          "BasicFileAttributeViewUnixSocketFile",
+          "p/pMain",
+          "PathMatcherBasic",
+          "walkFileTreeWalkWithSecurity",
+          "FilesFileAttributes",
+          "FilesInterruptCopy",
+          "FilesTemporaryFiles",
+          "FilesCheckPermissions",
+          "FilesMisc",
+          "WatchServiceWithSecurityManager",
+          "WatchServiceUpdateInterference",
+          "WatchServiceLotsOfCloses");
+  private static final List<String> SUCCESSFUL_TESTNG_TESTS =
+      ImmutableList.of("FilesStreamTest", "FilesBytesAndLines");
+  private static final List<String> FAILING_TESTNG_TESTS =
+      ImmutableList.of("spiSetDefaultProvider", "FilesReadWriteString");
+
+  @BeforeClass
+  public static void compileJdk11NioTests() throws Exception {
+    Map<String, List<Path>> nioTestFileBuckets = getSourceFileBuckets();
+    TEST_UTIL_JAR = jarTestUtils(nioTestFileBuckets.get("file"));
+    nioTestFileBuckets.remove("file");
+    TEST_PROGRAM_CLASS_DATA = new ArrayList<>();
+    for (Entry<String, List<Path>> entry : nioTestFileBuckets.entrySet()) {
+      Path[] compiledClasses = compile(entry.getKey(), entry.getValue(), TEST_UTIL_JAR);
+      assert compiledClasses.length > 0;
+      TEST_PROGRAM_CLASS_DATA.addAll(repackage(entry.getKey(), compiledClasses));
+    }
+    assert !TEST_PROGRAM_CLASS_DATA.isEmpty();
+  }
+
+  @NotNull
+  private static Map<String, List<Path>> getSourceFileBuckets() throws IOException {
+    Map<String, List<Path>> nioTestFileBuckets =
+        Files.walk(JDK_11_NIO_TEST_FILES_DIR)
+            .filter(path -> path.toString().endsWith(JAVA_EXTENSION))
+            .filter(
+                path ->
+                    EXCLUDE_COMPILATION.stream().noneMatch(elem -> path.toString().endsWith(elem)))
+            .collect(Collectors.groupingBy(item -> item.getParent().getFileName().toString()));
+    assert nioTestFileBuckets.size() > 0;
+    return nioTestFileBuckets;
+  }
+
+  private static Path jarTestUtils(List<Path> sourceFiles) throws IOException {
+    Path[] fileCompiledClasses = compile("file", sourceFiles, null);
+    String jarName = "testUtils.jar";
+    Path output = fileCompiledClasses[0].getParent();
+    List<String> cmdline = new ArrayList<>();
+    cmdline.add(TestRuntime.getCheckedInJdk11().getJavaExecutable().getParent() + "/jar");
+    cmdline.add("cf");
+    cmdline.add(jarName);
+    for (Path compile : fileCompiledClasses) {
+      cmdline.add(output.relativize(compile).toString());
+    }
+    ProcessBuilder builder = new ProcessBuilder(cmdline);
+    builder.directory(output.toFile());
+    ProcessResult result = ToolHelper.runProcess(builder);
+    assert result.exitCode == 0;
+    return output.resolve(jarName);
+  }
+
+  private static List<byte[]> repackage(String prefix, Path[] compiledClasses) throws IOException {
+    List<byte[]> data = new ArrayList<>();
+    Map<String, String> rewrite = new HashMap<>();
+    for (Path compiledClass : compiledClasses) {
+      String fileName = compiledClass.getFileName().toString();
+      String basicName = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
+      rewrite.put(basicName, prefix + basicName);
+    }
+    for (Path compiledClass : compiledClasses) {
+      String fileName = compiledClass.getFileName().toString();
+      String basicName = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
+      ClassFileTransformer classFileTransformer =
+          transformer(compiledClass, Reference.classFromDescriptor("L" + basicName + ";"))
+              .setClassDescriptor("L" + rewrite.get(basicName) + ";")
+              .setSuper(type -> rewrite.getOrDefault(type, type))
+              .rewriteEnlosingAndNestAttributes(type -> rewrite.getOrDefault(type, type));
+      rewrite.forEach(
+          (key, val) -> {
+            classFileTransformer.replaceClassDescriptorInMethodInstructions(
+                "L" + key + ";", "L" + val + ";");
+            classFileTransformer.replaceClassDescriptorInMembers("L" + key + ";", "L" + val + ";");
+          });
+      data.add(classFileTransformer.transform());
+    }
+    return data;
+  }
+
+  private static Path[] compile(String name, List<Path> sourceFiles, Path cp) throws IOException {
+    List<String> options =
+        Arrays.asList(
+            "--add-reads",
+            "java.base=ALL-UNNAMED",
+            "--patch-module",
+            "java.base=" + EXTENSION_PATH);
+    Path tmpDirectory = getStaticTemp().newFolder(name).toPath();
+    List<Path> classpath = new ArrayList<>();
+    classpath.add(EXTENSION_PATH);
+    classpath.add(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar"));
+    if (cp != null) {
+      classpath.add(cp);
+    }
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
+        .addOptions(options)
+        .addClasspathFiles(classpath)
+        .addSourceFiles(sourceFiles)
+        .setOutputPath(tmpDirectory)
+        .compile();
+    return getAllFilesWithSuffixInDirectory(tmpDirectory, CLASS_EXTENSION);
+  }
+
+  @Test
+  public void testNioFileDesugaredLib() throws Exception {
+    String verbosity = "2";
+    DesugaredLibraryTestCompileResult<?> compileResult =
+        testForDesugaredLibrary(
+                parameters, libraryDesugaringSpecification, compilationSpecification)
+            .addProgramFiles(TEST_UTIL_JAR)
+            .addProgramClassFileData(TEST_PROGRAM_CLASS_DATA)
+            .addProgramFiles(testNGSupportProgramFiles())
+            .compile()
+            .withArt6Plus64BitsLib();
+    int success = 0;
+    for (String mainTestClass : SUCCESSFUL_MAIN_TESTS) {
+      SingleTestRunResult<?> run = compileResult.run(parameters.getRuntime(), mainTestClass);
+      if (run.getExitCode() != 0) {
+        System.out.println("Main Fail " + mainTestClass);
+      } else {
+        success++;
+      }
+    }
+    for (String testNGTestClass : SUCCESSFUL_TESTNG_TESTS) {
+      SingleTestRunResult<?> result =
+          compileResult.run(
+              parameters.getRuntime(), "TestNGMainRunner", verbosity, testNGTestClass);
+      if (!result.getStdOut().contains(StringUtils.lines(testNGTestClass + ": SUCCESS"))) {
+        System.out.println("TestNG Fail " + testNGTestClass);
+      } else {
+        success++;
+      }
+    }
+    // TODO(b/234689867): Understand and fix these issues.
+    // Most issues seem to come from the missing secure.properties file. This file is not accessed
+    // in all tests on all API levels, hence a different number of failures on each level.
+    assertTrue(success >= 15);
+  }
+
+  @Test
+  public void testNioFileAndroid() throws Exception {
+    Assume.assumeFalse(
+        "The package java.nio was not present on older devices, all tests fail.",
+        parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0));
+    String verbosity = "2";
+    D8TestCompileResult compileResult =
+        testForD8(parameters.getBackend())
+            .addProgramFiles(TEST_UTIL_JAR)
+            .addProgramClassFileData(TEST_PROGRAM_CLASS_DATA)
+            .addProgramFiles(testNGSupportProgramFiles())
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+            .compile()
+            .withArt6Plus64BitsLib();
+    for (String mainTestClass : SUCCESSFUL_MAIN_TESTS) {
+      compileResult.run(parameters.getRuntime(), mainTestClass).assertSuccess();
+    }
+    for (String testNGTestClass : SUCCESSFUL_TESTNG_TESTS) {
+      SingleTestRunResult<?> result =
+          compileResult.run(
+              parameters.getRuntime(), "TestNGMainRunner", verbosity, testNGTestClass);
+      assertTrue(
+          "Failure in " + testNGTestClass + "\n" + result,
+          result.getStdOut().contains(StringUtils.lines(testNGTestClass + ": SUCCESS")));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java
index b6b3cb1..94b9162 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
 
 import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getPathsFiles;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getSafeVarArgsFile;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getPathsFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getSafeVarArgsFile;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
 import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.EXTENSION_PATH;
 import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.JDK11_PATH_JAVA_BASE_EXT;
 import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.JDK8_JAVA_BASE_EXT;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11Paths.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11SupportFiles.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11Paths.java
rename to src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11SupportFiles.java
index 1409b1b..d3507a2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11Paths.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11SupportFiles.java
@@ -14,7 +14,7 @@
 // Provides convenience to use Paths/SafeVarargs which are missing on old Android but
 // required by some Jdk tests, and for java.base extensions.
 
-public class Jdk11Paths {
+public class Jdk11SupportFiles {
 
   private static final Path ANDROID_PATHS_FILES_DIR =
       Paths.get("third_party/android_jar/lib-v26/xxx/");
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java
index c208900..6249cf1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java
@@ -32,10 +32,10 @@
 
   private static final String EXTENSION_STRING = "build/libs/java_base_extension.jar";
 
-  private static Path[] JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES;
-  private static Path JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR;
   private static final Path JDK_11_JAVA_BASE_EXTENSION_FILES_DIR =
       Paths.get("third_party/openjdk/jdk-11-test/lib/testlibrary/bootlib/java.base");
+  private static final Path JDK_11_TESTLIBRARY_FILES_DIR =
+      Paths.get("third_party/openjdk/jdk-11-test/lib/testlibrary/jdk");
 
   public static Path EXTENSION_PATH;
 
@@ -43,14 +43,22 @@
   public static LibraryDesugaringSpecification JDK11_JAVA_BASE_EXT;
   public static LibraryDesugaringSpecification JDK11_PATH_JAVA_BASE_EXT;
 
-  private static Path[] getJavaBaseExtensionsFiles() throws Exception {
+  private static Path[] getExtensionsFiles() throws Exception {
     Path[] files =
         getAllFilesWithSuffixInDirectory(JDK_11_JAVA_BASE_EXTENSION_FILES_DIR, JAVA_EXTENSION);
     assert files.length > 0;
-    return files;
+    Path[] files2 = getAllFilesWithSuffixInDirectory(JDK_11_TESTLIBRARY_FILES_DIR, JAVA_EXTENSION);
+    assert files2.length > 0;
+    List<Path> paths = new ArrayList<>(Arrays.asList(files));
+    Collections.addAll(paths, files2);
+    return paths.toArray(new Path[0]);
   }
 
   public static void setUp() throws Exception {
+    if (ToolHelper.isWindows()) {
+      // The library configuration is not available on windows. Do not run anything.
+      return;
+    }
     EXTENSION_PATH = Paths.get(EXTENSION_STRING);
     ensureJavaBaseExtensionsCompiled();
     JDK8_JAVA_BASE_EXT = createSpecification("JDK8_JAVA_BASE_EXT", JDK8);
@@ -76,7 +84,7 @@
     TemporaryFolder folder =
         new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
     folder.create();
-    JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR = folder.newFolder("jdk11JavaBaseExt").toPath();
+    Path output = folder.newFolder("jdk11Ext").toPath();
     List<String> options =
         Arrays.asList(
             "--add-reads",
@@ -87,30 +95,29 @@
         .addOptions(options)
         .addClasspathFiles(
             Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
-        .addSourceFiles(getJavaBaseExtensionsFiles())
-        .setOutputPath(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR)
+        .addSourceFiles(getExtensionsFiles())
+        .setOutputPath(output)
         .compile();
-    JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES =
-        getAllFilesWithSuffixInDirectory(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR, CLASS_EXTENSION);
-    assert JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES.length > 0;
+    Path[] toCompile = getAllFilesWithSuffixInDirectory(output, CLASS_EXTENSION);
+    assert toCompile.length > 0;
 
     // Jar the contents.
     List<String> cmdline = new ArrayList<>();
     cmdline.add(TestRuntime.getCheckedInJdk11().getJavaExecutable().getParent() + "/jar");
     cmdline.add("cf");
     cmdline.add("tmp.jar");
-    for (Path compile : JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES) {
-      cmdline.add(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.relativize(compile).toString());
+    for (Path compile : toCompile) {
+      cmdline.add(output.relativize(compile).toString());
     }
     ProcessBuilder builder = new ProcessBuilder(cmdline);
-    builder.directory(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.toFile());
+    builder.directory(output.toFile());
     ProcessResult result = ToolHelper.runProcess(builder);
     assert result.exitCode == 0;
 
     // Move the result into the build/libs folder.
     List<String> cmdlineMv = new ArrayList<>();
     cmdlineMv.add("mv");
-    cmdlineMv.add(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.resolve("tmp.jar").toString());
+    cmdlineMv.add(output.resolve("tmp.jar").toString());
     cmdlineMv.add(EXTENSION_STRING);
     ProcessResult resultMv = ToolHelper.runProcess(new ProcessBuilder(cmdlineMv));
     assert resultMv.exitCode == 0;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
index 6cf5f77..ee4cad8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
 
 import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getPathsFiles;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getPathsFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
 import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.EXTENSION_PATH;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index c7cda9c..9567481 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -4,30 +4,32 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.r8ondex;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.D8;
-import com.android.tools.r8.D8TestBuilder;
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.D8TestRunResult;
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.R8;
-import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestCompileResult;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -57,17 +59,27 @@
   }
 
   private final TestParameters parameters;
+  private final CompilationSpecification compilationSpecification;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
-        .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
-        .build();
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+            .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+            .build(),
+        ImmutableList.of(JDK8, JDK11_PATH),
+        ImmutableList.of(D8_L8DEBUG));
   }
 
-  public HelloWorldCompiledOnArtTest(TestParameters parameters) {
+  public HelloWorldCompiledOnArtTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
     this.parameters = parameters;
+    this.compilationSpecification = compilationSpecification;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
   }
 
   private static String commandLinePathFor(String string) {
@@ -101,18 +113,17 @@
   @Test
   public void testHelloCompiledWithD8Dex() throws Exception {
     Path helloOutput = temp.newFolder("helloOutput").toPath().resolve("out.zip").toAbsolutePath();
-    D8TestRunResult run =
-        compileR8ToDexWithD8()
-            .run(
-                parameters.getRuntime(),
-                D8.class,
-                "--release",
-                "--output",
-                helloOutput.toString(),
-                "--lib",
-                commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
-                HELLO_PATH);
-    run.assertSuccess();
+    compileR8ToDexWithD8()
+        .run(
+            parameters.getRuntime(),
+            D8.class,
+            "--release",
+            "--output",
+            helloOutput.toString(),
+            "--lib",
+            commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
+            HELLO_PATH)
+        .assertSuccess();
     verifyResult(helloOutput);
   }
 
@@ -121,32 +132,29 @@
     assertEquals(StringUtils.lines("Hello, world"), processResult.stdout);
   }
 
-  private D8TestCompileResult compileR8ToDexWithD8() throws Exception {
-    D8TestBuilder d8TestBuilder =
-        testForD8().addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR);
-    if (parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel()) {
-      d8TestBuilder.addProgramFiles(getPathBackport());
-    }
-    D8TestCompileResult compile =
-        d8TestBuilder
-            .addLibraryFiles(getLibraryFile())
-            .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(
-                LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-            .addOptionsModification(
-                options -> {
-                  options.testing.enableD8ResourcesPassThrough = true;
-                  options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
-                  options.testing.trackDesugaredAPIConversions = true;
-                })
-            .compile();
-    TestDiagnosticMessages diagnosticMessages = compile.getDiagnosticMessages();
-    assertTrue(
-        diagnosticMessages.getWarnings().isEmpty()
-            || diagnosticMessages.getWarnings().stream()
-                .noneMatch(x -> x.getDiagnosticMessage().contains("andThen")));
-    return compile
-        .withArt6Plus64BitsLib()
-        .withArtFrameworks();
+  private DesugaredLibraryTestCompileResult<?> compileR8ToDexWithD8() throws Exception {
+    Path[] pathBackport = getPathBackport();
+    return testForDesugaredLibrary(
+            parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
+        .applyIf(
+            parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel()
+                && libraryDesugaringSpecification != JDK11_PATH,
+            b -> b.addProgramFiles(pathBackport))
+        .addOptionsModification(
+            options -> {
+              options.testing.enableD8ResourcesPassThrough = true;
+              options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+              options.testing.trackDesugaredAPIConversions = true;
+            })
+        .compile()
+        .inspectDiagnosticMessages(
+            diagnosticMessages -> {
+              assertTrue(
+                  diagnosticMessages.getWarnings().isEmpty()
+                      || diagnosticMessages.getWarnings().stream()
+                          .noneMatch(x -> x.getDiagnosticMessage().contains("andThen")));
+            })
+        .withArt6Plus64BitsLib();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
index e72e4e7..b9ca3f1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
@@ -70,10 +70,8 @@
         .setMode(compilationSpecification.getProgramCompilationMode());
     LibraryDesugaringTestConfiguration.Builder libraryConfBuilder =
         LibraryDesugaringTestConfiguration.builder()
-            .setMinApi(parameters.getApiLevel())
             .addDesugaredLibraryConfiguration(
-                StringResource.fromFile(libraryDesugaringSpecification.getSpecification()))
-            .dontAddRunClasspath();
+                StringResource.fromFile(libraryDesugaringSpecification.getSpecification()));
     if (compilationSpecification.isL8Shrink() && !compilationSpecification.isCfToCf()) {
       keepRuleConsumer = new TestingKeepRuleConsumer();
       libraryConfBuilder.setKeepRuleConsumer(keepRuleConsumer);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
index 65171c1..edff04e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
@@ -3,9 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.desugaredlibrary.test;
 
+import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_11_LIB_JAR;
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
 import static com.android.tools.r8.ToolHelper.DESUGARED_LIB_RELEASES_DIR;
-import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
+import static com.android.tools.r8.ToolHelper.getUndesugaredJdk11LibJarForTesting;
 
 import com.android.tools.r8.L8TestBuilder;
 import com.android.tools.r8.ToolHelper;
@@ -25,17 +26,20 @@
           "JDK8", DESUGARED_JDK_8_LIB_JAR, "desugar_jdk_libs.json", AndroidApiLevel.P);
   public static LibraryDesugaringSpecification JDK11 =
       new LibraryDesugaringSpecification(
-          "JDK11", UNDESUGARED_JDK_11_LIB_JAR, "jdk11/desugar_jdk_libs.json", AndroidApiLevel.R);
+          "JDK11",
+          getUndesugaredJdk11LibJarForTesting(),
+          "jdk11/desugar_jdk_libs.json",
+          AndroidApiLevel.R);
   public static LibraryDesugaringSpecification JDK11_MINIMAL =
       new LibraryDesugaringSpecification(
           "JDK11_MINIMAL",
-          UNDESUGARED_JDK_11_LIB_JAR,
+          getUndesugaredJdk11LibJarForTesting(),
           "jdk11/desugar_jdk_libs_minimal.json",
           AndroidApiLevel.R);
   public static LibraryDesugaringSpecification JDK11_PATH =
       new LibraryDesugaringSpecification(
           "JDK11_PATH",
-          UNDESUGARED_JDK_11_LIB_JAR,
+          getUndesugaredJdk11LibJarForTesting(),
           "jdk11/desugar_jdk_libs_path.json",
           AndroidApiLevel.R);
 
@@ -43,20 +47,20 @@
   public static LibraryDesugaringSpecification JDK11_PATH_ALTERNATIVE_3 =
       new LibraryDesugaringSpecification(
           "JDK11_PATH_ALTERNATIVE_3",
-          UNDESUGARED_JDK_11_LIB_JAR,
+          getUndesugaredJdk11LibJarForTesting(),
           "jdk11/desugar_jdk_libs_path_alternative_3.json",
           AndroidApiLevel.R);
   public static LibraryDesugaringSpecification JDK11_CHM_ONLY =
       new LibraryDesugaringSpecification(
           "JDK11_CHM_ONLY",
-          UNDESUGARED_JDK_11_LIB_JAR,
+          getUndesugaredJdk11LibJarForTesting(),
           "jdk11/chm_only_desugar_jdk_libs.json",
           AndroidApiLevel.R);
   public static LibraryDesugaringSpecification JDK11_LEGACY =
       new LibraryDesugaringSpecification(
           "JDK11_LEGACY",
           // The legacy specification is not using the undesugared JAR.
-          Paths.get("third_party/openjdk/desugar_jdk_libs_11/desugar_jdk_libs.jar"),
+          DESUGARED_JDK_11_LIB_JAR,
           "jdk11/desugar_jdk_libs_legacy.json",
           AndroidApiLevel.R);
   public static final LibraryDesugaringSpecification RELEASED_1_0_9 =
@@ -141,8 +145,7 @@
     l8TestBuilder
         .addProgramFiles(getDesugarJdkLibs())
         .addLibraryFiles(getLibraryFiles())
-        .setDesugaredLibraryConfiguration(getSpecification())
-        .noDefaultDesugarJDKLibs()
+        .setDesugaredLibrarySpecification(getSpecification())
         .applyIf(
             l8Shrink,
             builder -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaMethodsWithModifiedAccessTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaMethodsWithModifiedAccessTest.java
new file mode 100644
index 0000000..61b75de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaMethodsWithModifiedAccessTest.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2022, 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.desugar.lambdas;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isNative;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.function.Function;
+import org.junit.BeforeClass;
+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;
+
+// See b/234475018) for context.
+@RunWith(Parameterized.class)
+public class LambdaMethodsWithModifiedAccessTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private static final String LAMBDA_TO_PUBLIC = "lambda$withPublicLambdaMethod$0";
+  private static final String LAMBDA_TO_NATIVE = "lambda$withNativeLambdaMethod$1";
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("withPublicLambdaMethod", "UnsatisfiedLinkError: withNativeLambdaMethod");
+
+  @BeforeClass
+  public static void checkJavacLambdas() throws IOException {
+    CodeInspector inspector =
+        new CodeInspector(ToolHelper.getClassFileForTestClass(LambdaTest.class));
+    inspector.forAllClasses(clazz -> clazz.forAllMethods(System.out::println));
+    assertThat(
+        inspector.clazz(LambdaTest.class).uniqueMethodWithName(LAMBDA_TO_PUBLIC), isPrivate());
+    assertThat(
+        inspector.clazz(LambdaTest.class).uniqueMethodWithName(LAMBDA_TO_NATIVE), isPrivate());
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(
+        inspector.clazz(LambdaTest.class).uniqueMethodWithName(LAMBDA_TO_PUBLIC), isPublic());
+    assertThat(
+        inspector.clazz(LambdaTest.class).uniqueMethodWithName(LAMBDA_TO_NATIVE), isNative());
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(getTransformedLambdaTest())
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::inspect)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  // TODO(b/234475018): Compilation hits an AssertionError which checks expected javac generated
+  // lambda methods in LambdaDescriptor.lookupTargetMethod.
+  // assert target == null
+  //     || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
+  //     || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
+  //     || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
+  @Test(expected = CompilationFailedException.class)
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(getTransformedLambdaTest())
+        .setMinApi(parameters.getApiLevel())
+        .compile();
+  }
+
+  // TODO(b/234475018): Compilation hits an AssertionError which checks expected javac generated
+  // lambda methods in LambdaDescriptor.lookupTargetMethod.
+  // assert target == null
+  //     || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
+  //     || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
+  //     || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
+  @Test(expected = CompilationFailedException.class)
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(getTransformedLambdaTest())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile();
+  }
+
+  private byte[] getTransformedLambdaTest() throws Exception {
+    return transformer(LambdaTest.class)
+        .setAccessFlags(MethodPredicate.onName(LAMBDA_TO_NATIVE), MethodAccessFlags::setNative)
+        .setAccessFlags(MethodPredicate.onName(LAMBDA_TO_PUBLIC), AccessFlags::promoteToPublic)
+        .removeMethodsCodeAndAnnotations(MethodPredicate.onName(LAMBDA_TO_NATIVE))
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new LambdaTest().withPublicLambdaMethod().apply(null);
+      try {
+        new LambdaTest().withNativeLambdaMethod().apply(null);
+      } catch (UnsatisfiedLinkError e) {
+        if (e.getMessage().contains(LAMBDA_TO_NATIVE)) {
+          System.out.println("UnsatisfiedLinkError: withNativeLambdaMethod");
+        } else {
+          System.out.println("UnsatisfiedLinkError: with unexpected content");
+        }
+      }
+    }
+  }
+
+  public static class LambdaTest {
+    String f;
+
+    Function<Void, String> withPublicLambdaMethod() {
+      return (ignored) -> {
+        System.out.println("withPublicLambdaMethod");
+        return f;
+      };
+    }
+
+    Function<Void, String> withNativeLambdaMethod() {
+      return (ignored) -> f;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 3e44232..2554cf0 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -4,15 +4,20 @@
 
 package com.android.tools.r8.desugar.records;
 
-import com.android.tools.r8.R8FullTestBuilder;
+import static org.junit.Assume.assumeFalse;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
 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 EmptyRecordTest extends TestBase {
@@ -21,17 +26,20 @@
   private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
   private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
   private static final String EXPECTED_RESULT_D8 = StringUtils.lines("Empty[]");
-  private static final String EXPECTED_RESULT_R8 = StringUtils.lines("a[]");
+  private static final String EXPECTED_RESULT_R8_MINIFICATION = StringUtils.lines("a[]");
+  private static final String EXPECTED_RESULT_R8_NO_MINIFICATION =
+      StringUtils.lines("EmptyRecord$Empty[]");
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public boolean enableMinification;
 
-  public EmptyRecordTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
+  @Parameter(1)
+  public TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameters(name = "{1}, minification: {0}")
   public static List<Object[]> data() {
     return buildParameters(
+        BooleanUtils.values(),
         getTestParameters()
             .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
@@ -41,6 +49,7 @@
 
   @Test
   public void testD8AndJvm() throws Exception {
+    assumeFalse("Only applicable for R8", enableMinification);
     if (parameters.isCfRuntime()) {
       testForJvm()
           .addProgramClassFileData(PROGRAM_DATA)
@@ -57,20 +66,28 @@
 
   @Test
   public void testR8() throws Exception {
-    R8FullTestBuilder builder =
-        testForR8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA)
-            .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(MAIN_TYPE);
-    if (parameters.isCfRuntime()) {
-      builder
-          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-          .compile()
-          .inspect(RecordTestUtils::assertRecordsAreRecords)
-          .run(parameters.getRuntime(), MAIN_TYPE)
-          .assertSuccessWithOutput(EXPECTED_RESULT_R8);
-      return;
-    }
-    builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT_R8);
+    // TODO(b/233857841): Should always succeed.
+    AssertUtils.assertFailsCompilationIf(
+        parameters.isDexRuntime() && !enableMinification,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClassFileData(PROGRAM_DATA)
+                .addKeepMainRule(MAIN_TYPE)
+                .applyIf(
+                    parameters.isCfRuntime(),
+                    testBuilder ->
+                        testBuilder.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)))
+                .minification(enableMinification)
+                .setMinApi(parameters.getApiLevel())
+                .compile()
+                .applyIf(
+                    parameters.isCfRuntime(),
+                    compileResult ->
+                        compileResult.inspect(RecordTestUtils::assertRecordsAreRecords))
+                .run(parameters.getRuntime(), MAIN_TYPE)
+                .assertSuccessWithOutput(
+                    enableMinification
+                        ? EXPECTED_RESULT_R8_MINIFICATION
+                        : EXPECTED_RESULT_R8_NO_MINIFICATION));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
new file mode 100644
index 0000000..78fc404
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2022, 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.desugar.sealed;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+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;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PermittedSubclassesAttributeInDexTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true");
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeTrue(
+        parameters.isCfRuntime()
+            && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17)
+            && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+    testForJvm()
+        .addProgramClassFileData(getTransformedClasses())
+        .addProgramClasses(Sub1.class, Sub2.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertEquals(
+        ImmutableList.of(
+            inspector.clazz(Sub1.class).asTypeSubject(),
+            inspector.clazz(Sub2.class).asTypeSubject()),
+        inspector.clazz(C.class).getFinalPermittedSubclassAttributes());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .addProgramClasses(Sub1.class, Sub2.class)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.emitPermittedSubclassesAnnotationsInDex = true)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        // No Art versions have support for sealed classes yet.
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  public Collection<byte[]> getTransformedClasses() throws Exception {
+    ClassFileTransformer transformer =
+        transformer(TestClass.class)
+            .setMinVersion(CfVm.JDK17)
+            .transformMethodInsnInMethod(
+                "main",
+                ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+                  if (owner.equals(DescriptorUtils.getClassBinaryName(AdditionalClassAPIs.class))) {
+                    if (name.equals("getPermittedSubclasses")) {
+                      continuation.visitMethodInsn(
+                          Opcodes.INVOKEVIRTUAL,
+                          "java/lang/Class",
+                          "getPermittedSubclasses",
+                          "()[Ljava/lang/Class;",
+                          false);
+                    } else if (name.equals("isSealed")) {
+                      continuation.visitMethodInsn(
+                          Opcodes.INVOKEVIRTUAL, "java/lang/Class", "isSealed", "()Z", false);
+                    } else {
+                      fail("Unsupported rewriting of API " + owner + "." + name + descriptor);
+                    }
+                  } else {
+                    continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                  }
+                }));
+
+    return ImmutableList.of(
+        transformer.transform(),
+        transformer(C.class).setPermittedSubclasses(C.class, Sub1.class, Sub2.class).transform());
+  }
+
+  static class AdditionalClassAPIs {
+    public static Class<?>[] getPermittedSubclasses(Class<?> clazz) {
+      throw new RuntimeException();
+    }
+
+    public static boolean isSealed(Class<?> clazz) {
+      throw new RuntimeException();
+    }
+  }
+
+  static class TestClass {
+
+    public static boolean sameArrayContent(Class<?>[] array1, Class<?>[] array2) {
+      Set<Class<?>> expected = new HashSet<>(Arrays.asList(array1));
+      for (Class<?> clazz : array2) {
+        if (!expected.remove(clazz)) {
+          return false;
+        }
+      }
+      return expected.isEmpty();
+    }
+
+    public static void main(String[] args) {
+      System.out.println(AdditionalClassAPIs.isSealed(C.class));
+      System.out.println(
+          sameArrayContent(
+              new Class<?>[] {Sub1.class, Sub2.class},
+              AdditionalClassAPIs.getPermittedSubclasses(C.class)));
+    }
+  }
+
+  static class C {}
+
+  static class Sub1 extends C {}
+
+  static class Sub2 extends C {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
index 3a5ba83..e57e215 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
@@ -10,76 +10,76 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.R8FullTestBuilder;
 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.examples.jdk17.Sealed;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.List;
+import com.android.tools.r8.utils.StringUtils;
 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 SealedAttributeTest extends TestBase {
 
-  private final TestParameters parameters;
-  private final Backend backend;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameters(name = "{0}, backend:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK17).build(),
-        Backend.values());
-  }
+  static final String EXPECTED = StringUtils.lines("R8 compiler", "D8 compiler");
 
-  public SealedAttributeTest(TestParameters parameters, Backend backend) {
-    this.parameters = parameters;
-    this.backend = backend;
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
   @Test
   public void testJvm() throws Exception {
-    assumeTrue(backend == Backend.CF);
+    assumeTrue(parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17));
     testForJvm()
         .addRunClasspathFiles(Sealed.jar())
         .run(parameters.getRuntime(), Sealed.Main.typeName())
-        .assertSuccessWithOutputLines("R8 compiler", "D8 compiler");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
-  public void testD8() throws Exception {
-    assertThrows(
-        CompilationFailedException.class,
-        () -> {
-          testForD8(backend)
-              .addProgramFiles(Sealed.jar())
-              .setMinApi(AndroidApiLevel.B)
-              .compileWithExpectedDiagnostics(
-                  diagnostics -> {
-                    diagnostics.assertErrorThatMatches(
-                        diagnosticMessage(
-                            containsString("Sealed classes are not supported as program classes")));
-                  });
-        });
+  public void testDesugaring() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramFiles(Sealed.jar())
+        .run(parameters.getRuntime(), Sealed.Main.typeName())
+        .applyIf(
+            c ->
+                DesugarTestConfiguration.isNotJavac(c)
+                    || parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK17),
+            r -> r.assertSuccessWithOutput(EXPECTED),
+            r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
   }
 
   @Test
   public void testR8() throws Exception {
-    assertThrows(
-        CompilationFailedException.class,
-        () -> {
-          testForR8(backend)
-              .addProgramFiles(Sealed.jar())
-              .setMinApi(AndroidApiLevel.B)
-              .addKeepMainRule(Sealed.Main.typeName())
-              .compileWithExpectedDiagnostics(
-                  diagnostics -> {
-                    diagnostics.assertErrorThatMatches(
-                        diagnosticMessage(
-                            containsString("Sealed classes are not supported as program classes")));
-                  });
-        });
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(Sealed.jar())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Sealed.Main.typeName());
+    if (parameters.isCfRuntime()) {
+      assertThrows(
+          CompilationFailedException.class,
+          () ->
+              builder.compileWithExpectedDiagnostics(
+                  diagnostics ->
+                      diagnostics.assertErrorThatMatches(
+                          diagnosticMessage(
+                              containsString(
+                                  "Sealed classes are not supported as program classes")))));
+    } else {
+      builder
+          .run(parameters.getRuntime(), Sealed.Main.typeName())
+          .assertSuccessWithOutput(EXPECTED);
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java
new file mode 100644
index 0000000..0cb64c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2022, 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.desugaring.interfacemethods;
+
+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 static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+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 DefaultMethodInvokeSuperOnDefaultLibraryMethodTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("1", "2");
+
+  private boolean runtimeHasConsumerInterface(TestParameters parameters) {
+    // java,util.function.Consumer was introduced at API level 24.
+    return parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+  }
+
+  @Test
+  public void testD8WithDefaultInterfaceMethodDesugaringWithAPIInLibrary() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        allOf(
+                            diagnosticType(StringDiagnostic.class),
+                            diagnosticMessage(
+                                containsString(
+                                    "Interface method desugaring has inserted NoSuchMethodError"
+                                        + " replacing a super call in")),
+                            diagnosticMessage(containsString("forEachPrint")))))
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            // If the platform does not have java.util.function.Consumer the lambda instantiation
+            // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+            // Otherwise, the generated code will throw NoSuchMethodError.
+            runtimeHasConsumerInterface(parameters),
+            b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testD8WithDefaultInterfaceMethodDesugaringWithoutAPIInLibrary() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.M))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class)))
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            // If the platform does not have java.util.function.Consumer the lambda instantiation
+            // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+            // Otherwise, the generated code will throw NoSuchMethodError.
+            runtimeHasConsumerInterface(parameters),
+            b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testD8WithDefaultInterfaceMethodSupport() throws Exception {
+    assumeTrue(
+        parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.N)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8WithDefaultInterfaceMethodDesugaringWithAPIInLibrary() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  @Test
+  public void testR8WithDefaultInterfaceMethodDesugaringWithoutAPIInLibrary() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.M))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .addKeepMainRule(TestClass.class)
+        .addDontWarn(Consumer.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            // If the platform does not have java.util.function.Consumer the lambda instantiation
+            // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+            // Otherwise, the generated code will throw NoSuchMethodError.
+            runtimeHasConsumerInterface(parameters),
+            b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testR8WithDefaultInterfaceMethodSupport() throws Exception {
+    assumeTrue(
+        parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addInnerClasses(getClass())
+        .setMinApi(AndroidApiLevel.N)
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  interface IntegerIterable extends Iterable<Integer> {
+    default void forEachPrint() {
+      Iterable.super.forEach(System.out::println);
+    }
+  }
+
+  static class IntegerIterable1And2 implements IntegerIterable {
+
+    @Override
+    public Iterator<Integer> iterator() {
+      List<Integer> result = new ArrayList<>();
+      result.add(1);
+      result.add(2);
+      return result.iterator();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new IntegerIterable1And2().forEachPrint();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
index ee0e845..9b23bd1 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -7,7 +7,6 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner;
@@ -28,6 +27,7 @@
 import java.io.InputStream;
 import java.util.Collections;
 import java.util.List;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.objectweb.asm.ClassReader;
@@ -137,10 +137,18 @@
         ToolHelper.getClassAsBytes(TestMainDefault0.class));
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.V13_0_0) // No desugaring
   public void testInvokeDefault1() throws Exception {
-    ensureSameOutput(
+    ensureCustomCheck(
+        (javaResult, d8Result, r8Result, r8ShakenResult) -> {
+          Assert.assertEquals(1, d8Result.exitCode);
+          Assert.assertTrue(d8Result.stderr.contains("NoSuchMethodError"));
+          Assert.assertEquals(1, r8Result.exitCode);
+          Assert.assertTrue(r8Result.stderr.contains("NoSuchMethodError"));
+          // R8 can determine that the super interface invokes are all overridden in the program
+          Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
+        },
         TestMainDefault1.class.getCanonicalName(),
         ToolHelper.getMinApiLevelForDexVm(),
         getArgs(AndroidApiLevel.N.getLevel()),
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/CheckEnumUnboxedTest.java b/src/test/java/com/android/tools/r8/enumunboxing/CheckEnumUnboxedTest.java
new file mode 100644
index 0000000..c5a786c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/CheckEnumUnboxedTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2022, 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.enumunboxing;
+
+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.equalTo;
+
+import com.android.tools.r8.CheckEnumUnboxed;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.CheckEnumUnboxedDiagnostic;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
+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 CheckEnumUnboxedTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    AssertUtils.assertFailsCompilation(
+        () ->
+            testForR8(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .addKeepMainRule(Main.class)
+                .enableCheckEnumUnboxedAnnotations()
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      diagnostics.assertErrorsMatch(
+                          allOf(
+                              diagnosticType(CheckEnumUnboxedDiagnostic.class),
+                              diagnosticMessage(
+                                  equalTo(
+                                      "Enum unboxing checks failed."
+                                          + System.lineSeparator()
+                                          + "Enum "
+                                          + EscapingEnum.class.getTypeName()
+                                          + " was not unboxed."))));
+                    }));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      EscapingEnum escapingEnum = System.currentTimeMillis() > 0 ? EscapingEnum.A : EscapingEnum.B;
+      System.out.println(escapingEnum);
+      UnboxedEnum unboxedEnum = System.currentTimeMillis() > 0 ? UnboxedEnum.A : UnboxedEnum.B;
+      System.out.println(unboxedEnum.ordinal());
+    }
+  }
+
+  @CheckEnumUnboxed
+  enum EscapingEnum {
+    A,
+    B
+  }
+
+  @CheckEnumUnboxed
+  enum UnboxedEnum {
+    A,
+    B
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
index c412e9f..56aed18 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
@@ -121,11 +121,9 @@
         .setMinApi(getApiLevel())
         .enableCoreLibraryDesugaring(
             LibraryDesugaringTestConfiguration.builder()
-                .setMinApi(getApiLevel())
                 .setKeepRuleConsumer(keepRuleConsumer)
                 .addDesugaredLibraryConfiguration(
                     StringResource.fromFile(getDesugaredLibraryConfiguration()))
-                .dontAddRunClasspath()
                 .build())
         .compile()
         .assertAllInfoMessagesMatch(
@@ -139,8 +137,8 @@
   private L8TestCompileResult compileDesugaredLibraryWithL8(KeepRuleConsumer keepRuleConsumer)
       throws CompilationFailedException, IOException, ExecutionException {
     return testForL8(getApiLevel())
-        .setDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
-        .setDesugarJDKLibs(getDesugaredLibraryJDKLibs())
+        .setDesugaredLibrarySpecification(getDesugaredLibraryConfiguration())
+        .addProgramFiles(getDesugaredLibraryJDKLibs())
         .addGeneratedKeepRules(keepRuleConsumer.get())
         .addKeepRuleFiles(getDesugaredLibraryKeepRuleFiles())
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
index 493df28..9d13687 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -34,6 +34,7 @@
           null,
           null,
           Collections.emptyList(),
+          Collections.emptyList(),
           null,
           Collections.emptyList(),
           ClassSignature.noSignature(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java
index 1231e9b..87961dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java
@@ -44,7 +44,7 @@
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-whyareyounotinlining class " + A.class.getTypeName() + " { void m(); }")
         .addOptionsModification(options -> options.testing.whyAreYouNotInliningConsumer = out)
-        .enableProguardTestOptions()
+        .enableExperimentalWhyAreYouNotInlining()
         .enableNoHorizontalClassMergingAnnotations()
         .compile();
     out.close();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java
index 2a420cf..99de3f5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java
@@ -35,7 +35,8 @@
 
   @Parameterized.Parameters(name = "{0}, with assume values rule: {1}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimes().build(), BooleanUtils.values());
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
   public LibraryFieldPropagationTest(TestParameters parameters, boolean withAssumeValuesRule) {
@@ -71,7 +72,7 @@
             withAssumeValuesRule
                 ? "-assumevalues class java.lang.Thread { public int MIN_PRIORITY return 1; }"
                 : "")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyFieldValueNotPropagated)
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MinSdkMemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MinSdkMemberValuePropagationTest.java
deleted file mode 100644
index 76f50e2..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MinSdkMemberValuePropagationTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2019, 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.membervaluepropagation;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-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 com.google.common.collect.ImmutableList;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class MinSdkMemberValuePropagationTest extends TestBase {
-
-  private final TestParameters parameters;
-  private final String rule;
-  private final String value;
-
-  @Parameterized.Parameters(name = "{0}, rule: {1}, value: {2}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimes().build(),
-        ImmutableList.of("assumenosideeffects", "assumevalues"),
-        ImmutableList.of("42", "42..43"));
-  }
-
-  public MinSdkMemberValuePropagationTest(TestParameters parameters, String rule, String value) {
-    this.parameters = parameters;
-    this.rule = rule;
-    this.value = value;
-  }
-
-  @Test
-  public void test() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(TestClass.class)
-        .addKeepMainRule(TestClass.class)
-        .addKeepRules(
-            "-" + rule + " class " + Library.class.getTypeName() + " {",
-            "  static int MIN_SDK return " + value + ";",
-            "}")
-        .addLibraryClasses(Library.class)
-        .addLibraryFiles(runtimeJar(parameters))
-        .setMinApi(parameters.getRuntime())
-        .compile()
-        .inspect(this::verifyOutput)
-        .addRunClasspathFiles(
-            testForR8(parameters.getBackend())
-                .addProgramClasses(Library.class)
-                .addKeepAllClassesRule()
-                .setMinApi(parameters.getRuntime())
-                .compile()
-                .writeToZip())
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello world!");
-  }
-
-  private void verifyOutput(CodeInspector inspector) {
-    ClassSubject classSubject = inspector.clazz(TestClass.class);
-    assertThat(classSubject, isPresent());
-
-    MethodSubject mainSubject = classSubject.mainMethod();
-    assertThat(mainSubject, isPresent());
-
-    boolean readsMinSdkField =
-        mainSubject
-            .streamInstructions()
-            .anyMatch(x -> x.isStaticGet() && x.getField().name.toString().equals("MIN_SDK"));
-    assertEquals(rule.equals("assumevalues"), readsMinSdkField);
-  }
-
-  static class TestClass {
-
-    public static void main(String[] args) {
-      if (Library.MIN_SDK >= 42) {
-        System.out.println("Hello world!");
-      }
-    }
-  }
-
-  static class Library {
-
-    static int MIN_SDK = -1;
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java
new file mode 100644
index 0000000..0d6c727
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java
@@ -0,0 +1,170 @@
+// Copyright (c) 2019, 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.membervaluepropagation;
+
+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.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class SdkIntMemberValuePropagationTest extends TestBase {
+
+  private enum Compiler {
+    D8,
+    R8
+  }
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public String rule;
+
+  @Parameterized.Parameters(name = "{0}, rule: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        ImmutableList.of(
+            "",
+            StringUtils.lines(
+                "-assumenosideeffects class android.os.Build$VERSION {",
+                "  public static int SDK_INT return 24;",
+                "}"),
+            StringUtils.lines(
+                "-assumenosideeffects class android.os.Build$VERSION {",
+                "  public static int SDK_INT return 24..25;",
+                "}"),
+            StringUtils.lines(
+                "-assumevalues class android.os.Build$VERSION {",
+                "  public static int SDK_INT return 24;",
+                "}"),
+            StringUtils.lines(
+                "-assumevalues class android.os.Build$VERSION {",
+                "  public static int SDK_INT return 24..29;",
+                "}")));
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    assumeTrue(rule.equals(""));
+    testForD8()
+        .addProgramClassFileData(getTransformedMainClass())
+        .addLibraryClassFileData(getTransformedBuildVERSIONClass())
+        .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> verifyOutput(inspector, Compiler.D8))
+        .addRunClasspathClassFileData(getTransformedBuildVERSIONClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutput(Compiler.D8));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedMainClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(rule)
+        .addLibraryClassFileData(getTransformedBuildVERSIONClass())
+        .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> verifyOutput(inspector, Compiler.R8))
+        .addRunClasspathClassFileData(getTransformedBuildVERSIONClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutput(Compiler.R8));
+  }
+
+  private String getExpectedOutput(Compiler compiler) {
+    if (compiler == Compiler.D8) {
+      return parameters.getApiLevel().isLessThan(AndroidApiLevel.N) ? "<N" : ">=N";
+    } else {
+      if (rule.equals("")) {
+        if (parameters.isDexRuntime()) {
+          return getExpectedOutput(Compiler.D8);
+        }
+        return "-1";
+      }
+      return ">=N";
+    }
+  }
+
+  private static byte[] getTransformedMainClass() throws IOException {
+    return transformer(Main.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(VERSION.class), "Landroid/os/Build$VERSION;")
+        .transform();
+  }
+
+  private byte[] getTransformedBuildVERSIONClass() throws IOException, NoSuchFieldException {
+    return transformer(VERSION.class)
+        .setClassDescriptor("Landroid/os/Build$VERSION;")
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+        .transform();
+  }
+
+  private void verifyOutput(CodeInspector inspector, Compiler compiler) {
+    ClassSubject classSubject = inspector.clazz(Main.class);
+    assertThat(classSubject, isPresent());
+
+    MethodSubject mainSubject = classSubject.mainMethod();
+    assertThat(mainSubject, isPresent());
+
+    boolean hasIf = mainSubject.streamInstructions().anyMatch(InstructionSubject::isIf);
+    boolean readsMinSdkField =
+        mainSubject
+            .streamInstructions()
+            .anyMatch(x -> x.isStaticGet() && x.getField().getName().toString().equals("SDK_INT"));
+    if (compiler == Compiler.D8 || rule.equals("")) {
+      assertEquals(
+          parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+          hasIf);
+      assertEquals(
+          parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+          readsMinSdkField);
+    } else {
+      assertFalse(hasIf);
+      assertEquals(rule.startsWith("-assumevalues"), readsMinSdkField);
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      if (VERSION.SDK_INT >= 24) {
+        System.out.println(">=N");
+      } else if (VERSION.SDK_INT >= 0) {
+        System.out.println("<N");
+      } else {
+        System.out.println("-1");
+      }
+    }
+  }
+
+  public static class /*android.os.Build$*/ VERSION {
+
+    public static /*final*/ int SDK_INT = -1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 2100549..90d7195 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -28,7 +28,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.LinkedList;
@@ -87,10 +86,7 @@
 
     @Override
     public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
-        AppView<AppInfoWithLiveness> appView,
-        IRCode code,
-        DexType type,
-        Consumer<InitClass> consumer) {
+        AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer) {
       throw new Unimplemented();
     }
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index ed70ece..5d54843 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -834,6 +834,7 @@
               null,
               null,
               Collections.emptyList(),
+              Collections.emptyList(),
               null,
               Collections.emptyList(),
               ClassSignature.noSignature(),
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java
index 29a2fce..e472056 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java
@@ -13,10 +13,13 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.nio.file.Path;
 import java.util.Collection;
 import org.junit.BeforeClass;
@@ -75,12 +78,24 @@
   }
 
   private void test(Path compileTimeLibrary, Path runtimeLibrary) throws Exception {
+    Method m = LibraryI.class.getDeclaredMethod("m");
+    MethodReference methodReferenceForM = Reference.methodFromMethod(m);
     testForR8(parameters.getBackend())
         .addProgramClasses(Main.class)
         .addKeepClassAndMembersRules(Main.class)
         .addLibraryFiles(compileTimeLibrary)
         .addDefaultRuntimeLibrary(parameters)
-        .apply(setMockApiLevelForMethod(LibraryI.class.getDeclaredMethod("m"), AndroidApiLevel.B))
+        .apply(setMockApiLevelForMethod(m, AndroidApiLevel.B))
+        // The api database needs to have an api level for each target, so even though B do not
+        // define 'm', we still give it an API level.
+        .apply(
+            setMockApiLevelForMethod(
+                Reference.method(
+                    Reference.classFromClass(LibraryB.class),
+                    methodReferenceForM.getMethodName(),
+                    methodReferenceForM.getFormalTypes(),
+                    methodReferenceForM.getReturnType()),
+                AndroidApiLevel.B))
         .apply(setMockApiLevelForMethod(LibraryC.class.getDeclaredMethod("m"), AndroidApiLevel.B))
         .apply(setMockApiLevelForMethod(LibraryA.class.getDeclaredMethod("m"), AndroidApiLevel.N))
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingSuperTypeWithApiMethodTest.java b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingSuperTypeWithApiMethodTest.java
new file mode 100644
index 0000000..c474de2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingSuperTypeWithApiMethodTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2022, 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.memberrebinding;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import javax.security.auth.DestroyFailedException;
+import javax.security.auth.Destroyable;
+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;
+
+/* Regression test for b/234613774 */
+@RunWith(Parameterized.class)
+public class LibraryMemberRebindingSuperTypeWithApiMethodTest extends TestBase {
+
+  private static final String SECRET_KEY_DESCRIPTOR = "Ljavax/crypto/SecretKey;";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getMainCallingSecretKeyDestroy())
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+        .setMinApi(AndroidApiLevel.B)
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0),
+            result -> result.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            result -> result.assertFailureWithErrorThatThrows(DestroyFailedException.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getMainCallingSecretKeyDestroy())
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+        .setMinApi(AndroidApiLevel.B)
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0),
+            result -> result.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            result -> result.assertFailureWithErrorThatThrows(DestroyFailedException.class));
+  }
+
+  private byte[] getMainCallingSecretKeyDestroy() throws Exception {
+    return transformer(Main.class)
+        .setImplementsClassDescriptors(SECRET_KEY_DESCRIPTOR)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(SecretKey.class), SECRET_KEY_DESCRIPTOR)
+        .removeMethods(MethodPredicate.onName("destroy"))
+        .transform();
+  }
+
+  public interface SecretKey extends Destroyable {
+
+    void destroy();
+  }
+
+  public static class Main implements /* javax.crypto */ SecretKey {
+
+    public static void main(String[] args) {
+
+      /* javax.crypto */ SecretKey main = new Main();
+      main.destroy();
+    }
+
+    @Override
+    public void destroy() {
+      throw new RuntimeException("Should have been removed");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
index a86f95a..ee69b91 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
@@ -72,12 +72,7 @@
               assertThat(
                   getSystemService,
                   CodeMatchers.invokesMethodWithHolderAndName(
-                      typeName(
-                          parameters.isCfRuntime()
-                                  || parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
-                              ? LibrarySubSub.class
-                              : LibrarySub.class),
-                      "getSystemService"));
+                      typeName(LibrarySubSub.class), "getSystemService"));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("LibrarySub::getSystemService");
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index f4158a1..4fdc4b7 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -71,6 +71,24 @@
       return this;
     }
 
+    public Builder addWithoutLineNumber(Class<?> clazz, String methodName, String fileName) {
+      return addWithoutLineNumber(clazz.getTypeName(), methodName, fileName);
+    }
+
+    public Builder addWithoutLineNumber(ClassReference clazz, String methodName, String fileName) {
+      return addWithoutLineNumber(clazz.getTypeName(), methodName, fileName);
+    }
+
+    public Builder addWithoutLineNumber(String className, String methodName, String fileName) {
+      stackTraceLines.add(
+          StackTraceLine.builder()
+              .setClassName(className)
+              .setMethodName(methodName)
+              .setFileName(fileName)
+              .build());
+      return this;
+    }
+
     public Builder map(int i, Function<StackTraceLine, StackTraceLine> map) {
       stackTraceLines.set(i, map.apply(stackTraceLines.get(i)));
       return this;
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineFunctionInPrunedClassTest.java b/src/test/java/com/android/tools/r8/retrace/InlineFunctionInPrunedClassTest.java
new file mode 100644
index 0000000..8daa8d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/InlineFunctionInPrunedClassTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2022, 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.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+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.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.Sets;
+import java.util.stream.Collectors;
+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 InlineFunctionInPrunedClassTest extends TestBase {
+
+  private static final String NEW_SOURCE_FILE = "SourceFileA.java";
+  private static final String ORIGINAL_SOURCE_FILE = "InlineFunctionInPrunedClassTest.java";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getAWithCustomSourceFile(), getMainWithStaticPosition())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(this::checkExpectedStackTrace);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getAWithCustomSourceFile(), getMainWithStaticPosition())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(
+            (stackTrace, inspector) -> {
+              // Ensure we have inlined the A class and it is not in the output.
+              assertEquals(
+                  Sets.newHashSet(typeName(Main.class)),
+                  inspector.allClasses().stream()
+                      .map(FoundClassSubject::getFinalName)
+                      .collect(Collectors.toSet()));
+              checkExpectedStackTrace(stackTrace);
+            });
+  }
+
+  private void checkExpectedStackTrace(StackTrace stackTrace) {
+    assertThat(
+        stackTrace,
+        isSame(
+            StackTrace.builder()
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName(typeName(A.class))
+                        .setMethodName("foo")
+                        .setFileName(NEW_SOURCE_FILE)
+                        .setLineNumber(1)
+                        .build())
+                .add(
+                    StackTraceLine.builder()
+                        .setClassName(typeName(Main.class))
+                        .setMethodName("main")
+                        .setFileName(ORIGINAL_SOURCE_FILE)
+                        .setLineNumber(1)
+                        .build())
+                .build()));
+  }
+
+  private static byte[] getAWithCustomSourceFile() throws Exception {
+    return transformer(A.class)
+        .setSourceFile(NEW_SOURCE_FILE)
+        .setPredictiveLineNumbering(MethodPredicate.all(), 1)
+        .transform();
+  }
+
+  private static byte[] getMainWithStaticPosition() throws Exception {
+    return transformer(Main.class).setPredictiveLineNumbering(MethodPredicate.all(), 1).transform();
+  }
+
+  public static class A {
+
+    public static void foo() {
+      throw new NullPointerException();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 3f97cd1..31dd142 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -38,8 +38,14 @@
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameWithInnerClassesStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineInOutlineStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineNoLineAssumeNoInlineAmbiguousStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberAssumeNoInlineStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlinePreambleNoOriginalStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlinePreambleWithOriginalStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineSourceFileContextStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineSourceFileStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MapVersionWarningStackTrace;
@@ -213,6 +219,31 @@
   }
 
   @Test
+  public void testInlineNoLineNumberAssumeNoInlineStackTrace() throws Exception {
+    runRetraceTest(new InlineNoLineNumberAssumeNoInlineStackTrace());
+  }
+
+  @Test
+  public void testInlineNoLineAssumeNoInlineAmbiguousStackTrace() throws Exception {
+    runRetraceTest(new InlineNoLineAssumeNoInlineAmbiguousStackTrace());
+  }
+
+  @Test
+  public void testInlinePreambleWithOriginalStackTrace() throws Exception {
+    runRetraceTest(new InlinePreambleWithOriginalStackTrace());
+  }
+
+  @Test
+  public void testInlinePreambleNoOriginalStackTrace() throws Exception {
+    runRetraceTest(new InlinePreambleNoOriginalStackTrace());
+  }
+
+  @Test
+  public void testInlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace() throws Exception {
+    runRetraceTest(new InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace());
+  }
+
+  @Test
   public void testCircularReferenceStackTrace() throws Exception {
     // Proguard retrace (and therefore the default regular expression) will not retrace circular
     // reference exceptions.
@@ -242,6 +273,11 @@
   }
 
   @Test
+  public void testInlineSourceFileStackTrace() throws Exception {
+    runRetraceTest(new InlineSourceFileStackTrace());
+  }
+
+  @Test
   public void testColonInSourceFileNameStackTrace() throws Exception {
     runRetraceTest(new ColonInFileNameStackTrace());
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineAssumeNoInlineAmbiguousStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineAssumeNoInlineAmbiguousStackTrace.java
new file mode 100644
index 0000000..515d6b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineAssumeNoInlineAmbiguousStackTrace.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineNoLineAssumeNoInlineAmbiguousStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat a.foo(Unknown Source)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        // TODO(b/231622686): Should not emit inline frame.
+        "\tat retrace.Main.method1(Main.java)",
+        "\tat retrace.Main.main(Main.java)",
+        "\t<OR> at retrace.Main.otherMain(Main.java)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        // TODO(b/231622686): Should not emit inline frame.
+        "\tat retrace.Main.void method1(java.lang.String)(Main.java:0)",
+        "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)",
+        "\t<OR> at retrace.Main.void otherMain(java.lang.String[])(Main.java)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "retrace.Main -> a:",
+        "    void otherMain(java.lang.String[]) -> foo",
+        "    2:2:void method1(java.lang.String):0:0 -> foo",
+        "    2:2:void main(java.lang.String[]):0 -> foo");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberAssumeNoInlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberAssumeNoInlineStackTrace.java
new file mode 100644
index 0000000..ffc5da2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberAssumeNoInlineStackTrace.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineNoLineNumberAssumeNoInlineStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat a.foo(Unknown Source)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    // TODO(b/231622686): Should assume that no lines means no inline frames.
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat retrace.Main.method1(Main.java)",
+        "\tat retrace.Main.main(Main.java)",
+        "\t<OR> at retrace.Main.method2(Main.java)",
+        "\tat retrace.Main.main(Main.java)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    // TODO(b/231622686): Should assume that no lines means no inline frames.
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat retrace.Main.void method1(java.lang.String)(Main.java:0)",
+        "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)",
+        "\t<OR> at retrace.Main.void method2(int)(Main.java:0)",
+        "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "retrace.Main -> a:",
+        "    1:1:void method1(java.lang.String):0:0 -> foo",
+        "    1:1:void main(java.lang.String[]):0 -> foo",
+        "    2:2:void method2(int):0:0 -> foo",
+        "    2:2:void main(java.lang.String[]):0 -> foo");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace.java
new file mode 100644
index 0000000..3e52ebd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat a.foo(Unknown Source)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    // TODO(b/231622686): Should assume that no lines means no inline frames.
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat retrace.Main.main(Main.java)",
+        "\t<OR> at retrace.Main.method1(Main.java)",
+        "\tat retrace.Main.main(Main.java)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    // TODO(b/231622686): Should assume that no lines means no inline frames.
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat retrace.Main.void main(java.lang.String[])(Main.java)",
+        "\t<OR> at retrace.Main.void method1(java.lang.String)(Main.java:0)",
+        "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "retrace.Main -> a:",
+        "    void main(java.lang.String[]) -> foo",
+        "    1:1:void method1(java.lang.String):0:0 -> foo",
+        "    1:1:void main(java.lang.String[]):0 -> foo");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleNoOriginalStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleNoOriginalStackTrace.java
new file mode 100644
index 0000000..47732b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleNoOriginalStackTrace.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlinePreambleNoOriginalStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat a.foo(Unknown Source)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        // TODO(b/231622686): Should only include preamble
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat retrace.Main.main(Main.java)",
+        "\t<OR> at retrace.Main.method1(Main.java)",
+        "\tat retrace.Main.main(Main.java)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        // TODO(b/231622686): Should only include preamble
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat retrace.Main.void main(java.lang.String[])(Main.java)",
+        "\t<OR> at retrace.Main.void method1(java.lang.String)(Main.java:0)",
+        "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "retrace.Main -> a:",
+        "    0:1:void main(java.lang.String[]) -> foo",
+        "    2:2:void method1(java.lang.String):0:0 -> foo",
+        "    2:2:void main(java.lang.String[]):0 -> foo");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleWithOriginalStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleWithOriginalStackTrace.java
new file mode 100644
index 0000000..72c2076
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleWithOriginalStackTrace.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlinePreambleWithOriginalStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat a.foo(Unknown Source)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        // TODO(b/231622686): Should only include preamble
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat retrace.Main.main(Main.java)",
+        "\t<OR> at retrace.Main.method1(Main.java)",
+        "\tat retrace.Main.main(Main.java)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        // TODO(b/231622686): Should only include preamble
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)",
+        "\t<OR> at retrace.Main.void method1(java.lang.String)(Main.java:0)",
+        "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "retrace.Main -> a:",
+        "    0:1:void main(java.lang.String[]):0:0 -> foo",
+        "    2:2:void method1(java.lang.String):0:0 -> foo",
+        "    2:2:void main(java.lang.String[]):0 -> foo");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileStackTrace.java
new file mode 100644
index 0000000..9128e47
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileStackTrace.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineSourceFileStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList("  at b.c(Unknown Source:1)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.joinLines(
+        "foo.Bar -> a:",
+        "# {\"id\":\"sourceFile\",\"fileName\":\"SourceFile1.kt\"}",
+        "foo.Baz -> b:",
+        "# {\"id\":\"sourceFile\",\"fileName\":\"SourceFile2.kt\"}",
+        "    1:1:void foo.Bar.method():22:22 -> c",
+        "    1:1:void main(java.lang.String[]):32 -> c");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "  at foo.Bar.method(SourceFile1.kt:22)", "  at foo.Baz.main(SourceFile2.kt:32)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "  at foo.Bar.void method()(SourceFile1.kt:22)",
+        "  at foo.Baz.void main(java.lang.String[])(SourceFile2.kt:32)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepNonConstructorsTest.java b/src/test/java/com/android/tools/r8/shaking/KeepNonConstructorsTest.java
new file mode 100644
index 0000000..a1f492b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/KeepNonConstructorsTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2022, 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 KeepNonConstructorsTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void keepConstructorTest() throws Exception {
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepRules("-keep class " + A.class.getTypeName() + " { constructor *** *(...); }")
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertThat(aClassSubject.init(), isPresent());
+              assertEquals(1, aClassSubject.allMethods().size());
+            });
+  }
+
+  @Test
+  public void keepNonConstructorsTest() throws Exception {
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepRules("-keep class " + A.class.getTypeName() + " { !constructor *** *(...); }")
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
+              assertEquals(1, aClassSubject.allMethods().size());
+            });
+  }
+
+  static class A {
+
+    A() {}
+
+    void m() {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java
new file mode 100644
index 0000000..23febd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2022, 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;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentHashMap.KeySetView;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideCovariantTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LibraryMethodOverrideCovariantTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean supportsKeySetView() {
+    return parameters.isCfRuntime()
+        || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V10_0_0);
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryUser.class))
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            supportsKeySetView(),
+            result -> result.assertFailureWithErrorThatMatches(containsString("Hello World")),
+            result -> result.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getMainWithoutSyntheticBridgeForKeySet())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryClasses(LibraryUser.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .compile()
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryUser.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatchesIf(
+            parameters.isCfRuntime(), containsString("Hello World"))
+        .assertFailureWithErrorThatThrowsIf(
+            !supportsKeySetView() && parameters.isDexRuntime(), NoSuchMethodError.class)
+        // TODO(b/234579501): We fail to keep the library method override.
+        .applyIf(
+            parameters.isDexRuntime() && supportsKeySetView(),
+            TestRunResult::assertSuccessWithEmptyOutput);
+  }
+
+  private byte[] getMainWithoutSyntheticBridgeForKeySet() throws Exception {
+    return transformer(Main.class)
+        .removeMethods(
+            (access, name, descriptor, signature, exceptions) ->
+                descriptor.equals("()Ljava/util/Set;"))
+        .transform();
+  }
+
+  public static class Main extends ConcurrentHashMap<String, String> {
+
+    @Override
+    @NeverInline
+    public KeySetView<String, String> keySet() {
+      throw new RuntimeException("Hello World");
+    }
+
+    public static void main(String[] args) {
+      LibraryUser.checkKeySet(new Main());
+    }
+  }
+
+  public static class LibraryUser {
+
+    public static void checkKeySet(ConcurrentHashMap<?, ?> map) {
+      KeySetView<?, ?> objects = map.keySet();
+    }
+  }
+}
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 6c05cb7..a841c79 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -183,7 +183,16 @@
   public void resetAllowTestOptions() {
     handler = new KeepingDiagnosticHandler();
     reporter = new Reporter(handler);
-    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, null, true);
+    parser =
+        new ProguardConfigurationParser(
+            new DexItemFactory(),
+            reporter,
+            ProguardConfigurationParserOptions.builder()
+                .setEnableExperimentalCheckEnumUnboxed(false)
+                .setEnableExperimentalConvertCheckNotNull(false)
+                .setEnableExperimentalWhyAreYouNotInlining(false)
+                .setEnableTestingOptions(true)
+                .build());
   }
 
   @Test
@@ -447,7 +456,7 @@
         assertTrue(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
+        assertFalse(rule.getReturnValue().isNullability());
         assertEquals(rule.getName().matches("returnsTrue"), rule.getReturnValue().getBoolean());
         matches |= 1 << 0;
       } else if (rule.getName().matches("returns1")) {
@@ -455,19 +464,16 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertTrue(rule.getReturnValue().isSingleValue());
+        assertFalse(rule.getReturnValue().isNullability());
         assertEquals(1, rule.getReturnValue().getValueRange().getMin());
         assertEquals(1, rule.getReturnValue().getValueRange().getMax());
-        assertEquals(1, rule.getReturnValue().getSingleValue());
         matches |= 1 << 1;
       } else if (rule.getName().matches("returns2To4")) {
         assertTrue(rule.hasReturnValue());
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertFalse(rule.getReturnValue().isSingleValue());
+        assertFalse(rule.getReturnValue().isNullability());
         assertEquals(2, rule.getReturnValue().getValueRange().getMin());
         assertEquals(4, rule.getReturnValue().getValueRange().getMax());
         matches |= 1 << 2;
@@ -476,8 +482,7 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertFalse(rule.getReturnValue().isSingleValue());
+        assertFalse(rule.getReturnValue().isNullability());
         assertEquals(234, rule.getReturnValue().getValueRange().getMin());
         assertEquals(567, rule.getReturnValue().getValueRange().getMax());
         matches |= 1 << 3;
@@ -486,31 +491,34 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertTrue(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
-        assertEquals("int", rule.getReturnValue().getField().type.toString());
-        assertEquals("X", rule.getReturnValue().getField().name.toString());
+        assertFalse(rule.getReturnValue().isNullability());
+        assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+        assertEquals("X", rule.getReturnValue().getFieldName().toString());
         matches |= 1 << 4;
       } else if (rule.getName().matches("returnsNull")) {
         assertTrue(rule.hasReturnValue());
         assertFalse(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertTrue(rule.getReturnValue().isNull());
-        assertTrue(rule.getReturnValue().isSingleValue());
+        assertTrue(rule.getReturnValue().isNullability());
+        assertTrue(rule.getReturnValue().getNullability().isDefinitelyNull());
         matches |= 1 << 5;
       } else if (rule.getName().matches("returnsNonNull")) {
-        assertFalse(rule.hasReturnValue());
+        assertTrue(rule.hasReturnValue());
+        assertFalse(rule.getReturnValue().isBoolean());
+        assertFalse(rule.getReturnValue().isValueRange());
+        assertFalse(rule.getReturnValue().isField());
+        assertTrue(rule.getReturnValue().isNullability());
+        assertTrue(rule.getReturnValue().getNullability().isDefinitelyNotNull());
         matches |= 1 << 6;
       } else if (rule.getName().matches("returnsNonNullField")) {
         assertTrue(rule.hasReturnValue());
         assertFalse(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertTrue(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
-        assertEquals("Object", rule.getReturnValue().getField().type.toString());
-        assertEquals("X", rule.getReturnValue().getField().name.toString());
+        assertFalse(rule.getReturnValue().isNullability());
+        assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+        assertEquals("X", rule.getReturnValue().getFieldName().toString());
         matches |= 1 << 7;
       } else {
         fail("Unexpected");
@@ -534,7 +542,7 @@
         assertTrue(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
+        assertFalse(rule.getReturnValue().isNullability());
         assertEquals(rule.getName().matches("isTrue"), rule.getReturnValue().getBoolean());
         matches |= 1 << 0;
       } else if (rule.getName().matches("is1")) {
@@ -542,19 +550,16 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertTrue(rule.getReturnValue().isSingleValue());
+        assertFalse(rule.getReturnValue().isNullability());
         assertEquals(1, rule.getReturnValue().getValueRange().getMin());
         assertEquals(1, rule.getReturnValue().getValueRange().getMax());
-        assertEquals(1, rule.getReturnValue().getSingleValue());
         matches |= 1 << 1;
       } else if (rule.getName().matches("is2To4")) {
         assertTrue(rule.hasReturnValue());
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertFalse(rule.getReturnValue().isSingleValue());
+        assertFalse(rule.getReturnValue().isNullability());
         assertEquals(2, rule.getReturnValue().getValueRange().getMin());
         assertEquals(4, rule.getReturnValue().getValueRange().getMax());
         matches |= 1 << 2;
@@ -563,8 +568,7 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertFalse(rule.getReturnValue().isSingleValue());
+        assertFalse(rule.getReturnValue().isNullability());
         assertEquals(234, rule.getReturnValue().getValueRange().getMin());
         assertEquals(567, rule.getReturnValue().getValueRange().getMax());
         matches |= 1 << 3;
@@ -573,31 +577,36 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertTrue(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
-        assertEquals("int", rule.getReturnValue().getField().type.toString());
-        assertEquals("X", rule.getReturnValue().getField().name.toString());
+        assertFalse(rule.getReturnValue().isNullability());
+        assertTrue(rule.getReturnValue().getNullability().isMaybeNull());
+        assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+        assertEquals("X", rule.getReturnValue().getFieldName().toString());
         matches |= 1 << 4;
       } else if (rule.getName().matches("isNull")) {
         assertTrue(rule.hasReturnValue());
         assertFalse(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
-        assertTrue(rule.getReturnValue().isNull());
-        assertTrue(rule.getReturnValue().isSingleValue());
+        assertTrue(rule.getReturnValue().isNullability());
+        assertTrue(rule.getReturnValue().getNullability().isDefinitelyNull());
         matches |= 1 << 5;
       } else if (rule.getName().matches("returnsNonNull")) {
-        assertFalse(rule.hasReturnValue());
+        assertTrue(rule.hasReturnValue());
+        assertFalse(rule.getReturnValue().isBoolean());
+        assertFalse(rule.getReturnValue().isValueRange());
+        assertFalse(rule.getReturnValue().isField());
+        assertTrue(rule.getReturnValue().isNullability());
+        assertTrue(rule.getReturnValue().getNullability().isDefinitelyNotNull());
         matches |= 1 << 6;
       } else if (rule.getName().matches("returnsNonNullField")) {
         assertTrue(rule.hasReturnValue());
         assertFalse(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertTrue(rule.getReturnValue().isField());
-        assertFalse(rule.getReturnValue().isNull());
-        assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
-        assertEquals("Object", rule.getReturnValue().getField().type.toString());
-        assertEquals("X", rule.getReturnValue().getField().name.toString());
+        assertFalse(rule.getReturnValue().isNullability());
+        assertTrue(rule.getReturnValue().getNullability().isDefinitelyNotNull());
+        assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+        assertEquals("X", rule.getReturnValue().getFieldName().toString());
         matches |= 1 << 7;
       } else {
         fail("Unexpected");
@@ -732,6 +741,48 @@
   }
 
   @Test
+  public void testConvertCheckNotNullWithReturn() {
+    DexItemFactory dexItemFactory = new DexItemFactory();
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(
+            dexItemFactory,
+            reporter,
+            ProguardConfigurationParserOptions.builder()
+                .setEnableExperimentalCheckEnumUnboxed(false)
+                .setEnableExperimentalConvertCheckNotNull(true)
+                .setEnableExperimentalWhyAreYouNotInlining(false)
+                .setEnableTestingOptions(false)
+                .build());
+    String rule = "-convertchecknotnull class C { ** m(**, ...); }";
+    parser.parse(createConfigurationForTesting(ImmutableList.of(rule)));
+    verifyParserEndsCleanly();
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(1, config.getRules().size());
+    assertTrue(config.getRules().get(0) instanceof ConvertCheckNotNullRule);
+  }
+
+  @Test
+  public void testConvertCheckNotNullWithoutReturn() {
+    DexItemFactory dexItemFactory = new DexItemFactory();
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(
+            dexItemFactory,
+            reporter,
+            ProguardConfigurationParserOptions.builder()
+                .setEnableExperimentalCheckEnumUnboxed(false)
+                .setEnableExperimentalConvertCheckNotNull(true)
+                .setEnableExperimentalWhyAreYouNotInlining(false)
+                .setEnableTestingOptions(false)
+                .build());
+    String rule = "-convertchecknotnull class C { void m(**, ...); }";
+    parser.parse(createConfigurationForTesting(ImmutableList.of(rule)));
+    verifyParserEndsCleanly();
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(1, config.getRules().size());
+    assertTrue(config.getRules().get(0) instanceof ConvertCheckNotNullRule);
+  }
+
+  @Test
   public void parseDontobfuscate() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -992,7 +1043,7 @@
   @Test
   public void parseKeepdirectories() {
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), reporter, null, false);
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(KEEPDIRECTORIES));
     verifyParserEndsCleanly();
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
index 8130a6f..b73fc3e 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
@@ -88,15 +88,8 @@
               ? StringUtils.lines(
                   "[AnotherSub2, debug]: message08", "[AnotherSub2, debug]: message5", "The end")
               : StringUtils.lines(
-                  // TODO(b/133208961): Introduce comparison/meet of assume rules.
-                  // Itf has side effects for all methods, since we don't compute the meet yet.
-                  "[Sub1, info]: message00",
                   "[Base1, debug]: message00",
-                  "[Sub1, verbose]: message00",
-                  "[Base2, info]: message08",
                   "[AnotherSub2, debug]: message08",
-                  "[AnotherSub2, verbose]: message08",
-                  // Base2#debug also has side effects.
                   "[AnotherSub2, debug]: message5",
                   "The end");
         case NON_SPECIFIC_RULES_ALL:
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeReturnsFieldWithSubtypingTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeReturnsFieldWithSubtypingTest.java
new file mode 100644
index 0000000..63ea0c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeReturnsFieldWithSubtypingTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2022, 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.assumevalues;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 AssumeReturnsFieldWithSubtypingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-assumevalues class " + Main.class.getTypeName() + " {",
+            "  java.lang.Object getGreeting() return " + Main.class.getTypeName() + ".greeting;",
+            "}",
+            // TODO(b/233828966): Maybe disallow shrinking of this in the first round of shaking.
+            "-keepclassmembers,allowobfuscation class " + Main.class.getTypeName() + "{",
+            "  java.lang.String greeting;",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    static String greeting = System.currentTimeMillis() > 0 ? "Hello world!" : null;
+
+    public static void main(String[] args) {
+      System.out.println(getGreeting());
+    }
+
+    static Object getGreeting() {
+      return "Unexpected";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMissingStaticFieldDiagnosticTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMissingStaticFieldDiagnosticTest.java
new file mode 100644
index 0000000..cc00fac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMissingStaticFieldDiagnosticTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2022, 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.assumevalues;
+
+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.equalTo;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.AssumeValuesMissingStaticFieldDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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 AssumeValuesMissingStaticFieldDiagnosticTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-assumevalues class " + Main.class.getTypeName() + " {",
+            "  static java.lang.Object get() return Missing.field;",
+            "}")
+        .allowDiagnosticWarningMessages()
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(AssumeValuesMissingStaticFieldDiagnostic.class),
+                        diagnosticMessage(
+                            equalTo(
+                                "The field Missing.field is used as the return value in an "
+                                    + "-assumenosideeffects or -assumevalues rule, but no such "
+                                    + "static field exists.")))));
+  }
+
+  static class Main {
+
+    static Object get() {
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
index b2f0964..9b5cc93 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
@@ -303,13 +303,17 @@
           "-assumevalues class android.os.Build$VERSION { *; }"
         };
 
-    for (String rule : rules) {
+    for (int ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {
+      final int finalRuleIndex = ruleIndex;
+      String rule = rules[ruleIndex];
       runTest(
           AndroidApiLevel.O_MR1,
           AndroidApiLevel.O_MR1,
           AndroidApiLevel.O_MR1,
           expectedResultForNative(AndroidApiLevel.O_MR1),
-          builder -> builder.allowUnusedProguardConfigurationRules(backend == Backend.CF),
+          builder ->
+              builder.allowUnusedProguardConfigurationRules(
+                  backend == Backend.CF || finalRuleIndex >= 4),
           this::compatCodePresent,
           ImmutableList.of(rule),
           SynthesizedRule.NOT_PRESENT);
diff --git a/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java b/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java
new file mode 100644
index 0000000..5aad9aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2022, 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.convertchecknotnull;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+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 ConvertCheckNotNullTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .applyIf(
+            parameters.isDexRuntime(),
+            testBuilder -> testBuilder.addLibraryFiles(ToolHelper.getMostRecentAndroidJar()))
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-convertchecknotnull class " + Main.class.getTypeName() + " {",
+            "  void requireNonNullWithoutReturn(**, ...);",
+            "  ** requireNonNullWithReturn(**, ...);",
+            "}",
+            "-convertchecknotnull class java.util.Objects {",
+            "  ** requireNonNull(**, ...);",
+            "}")
+        .enableExperimentalConvertCheckNotNull()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              assertEquals(
+                  6,
+                  mainMethodSubject
+                      .streamInstructions()
+                      .filter(
+                          CodeMatchers.isInvokeWithTarget(Object.class.getTypeName(), "getClass"))
+                      .count());
+
+              assertThat(
+                  mainClassSubject.uniqueMethodWithName("requireNonNullWithoutReturn"), isAbsent());
+              assertThat(
+                  mainClassSubject.uniqueMethodWithName("requireNonNullWithReturn"), isAbsent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private String getExpectedOutput() {
+    String message4 = "null";
+    String message5 = "null";
+    String message6 = "null";
+    if (parameters.isCfRuntime()) {
+      if (parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK17)) {
+        message4 = "Cannot invoke \"Object.getClass()\" because \"<local3>\" is null";
+        message5 = "Cannot invoke \"Object.getClass()\" because \"<local4>\" is null";
+        message6 = "Cannot invoke \"Object.getClass()\" because \"<local5>\" is null";
+      }
+    } else {
+      if (parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V8_1_0, Version.DEFAULT)) {
+        message4 =
+            message5 = message6 = "Attempt to invoke a virtual method on a null object reference";
+      } else if (parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V5_1_1)) {
+        message4 =
+            message5 =
+                message6 =
+                    "Attempt to invoke virtual method 'java.lang.Class"
+                        + " java.lang.Object.getClass()' on a null object reference";
+      }
+    }
+    return StringUtils.lines(
+        "Test #1", "Test #2", "Test #3", "Test #4", message4, "Test #5", message5, "Test #6",
+        message6);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Object p1 = System.currentTimeMillis() >= 0 ? new Object() : null;
+      Object p2 = System.currentTimeMillis() >= 0 ? new Object() : null;
+      Object p3 = System.currentTimeMillis() >= 0 ? new Object() : null;
+      Object p4 = System.currentTimeMillis() >= 0 ? null : new Object();
+      Object p5 = System.currentTimeMillis() >= 0 ? null : new Object();
+      Object p6 = System.currentTimeMillis() >= 0 ? null : new Object();
+
+      System.out.println("Test #1");
+      requireNonNullWithoutReturn(p1, "p1");
+
+      System.out.println("Test #2");
+      Object p2alias = requireNonNullWithReturn(p2, "p2");
+      if (p2alias != p2) {
+        throw new RuntimeException();
+      }
+
+      System.out.println("Test #3");
+      Object p3alias = Objects.requireNonNull(p3, "p3");
+      if (p3alias != p3) {
+        throw new RuntimeException();
+      }
+
+      System.out.println("Test #4");
+      try {
+        requireNonNullWithoutReturn(p4, "p4");
+      } catch (NullPointerException e) {
+        System.out.println(e.getMessage());
+      }
+
+      System.out.println("Test #5");
+      try {
+        Object p5alias = requireNonNullWithReturn(p5, "p5");
+        System.out.println(p5alias);
+      } catch (NullPointerException e) {
+        System.out.println(e.getMessage());
+      }
+
+      System.out.println("Test #6");
+      try {
+        Object p6alias = Objects.requireNonNull(p6, "p6");
+        System.out.println(p6alias);
+      } catch (NullPointerException e) {
+        System.out.println(e.getMessage());
+      }
+    }
+
+    static void requireNonNullWithoutReturn(Object object, String parameterName) {
+      if (object == null) {
+        throw new NullPointerException("Expected parameter " + parameterName + " to be non-null");
+      }
+    }
+
+    static Object requireNonNullWithReturn(Object object, String parameterName) {
+      if (object == null) {
+        throw new NullPointerException("Expected parameter " + parameterName + " to be non-null");
+      }
+      return object;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 52485b2..b4c6e12 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -6,9 +6,8 @@
 import static com.android.tools.r8.references.Reference.classFromTypeName;
 import static com.android.tools.r8.transformers.ClassFileTransformer.InnerClassPredicate.always;
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 import static com.android.tools.r8.utils.StringUtils.replaceAll;
-import static org.objectweb.asm.Opcodes.ASM7;
-import static org.objectweb.asm.Opcodes.ASM9;
 
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
@@ -32,7 +31,9 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -81,7 +82,7 @@
     final List<MethodTransformer> methodTransformers;
 
     InnerMostClassTransformer(ClassWriter writer, List<MethodTransformer> methodTransformers) {
-      super(ASM7, writer);
+      super(ASM_VERSION, writer);
       this.methodTransformers = methodTransformers;
     }
 
@@ -331,6 +332,22 @@
         });
   }
 
+  public ClassFileTransformer setSuper(Function<String, String> rewrite) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            super.visit(version, access, name, signature, rewrite.apply(superName), interfaces);
+          }
+        });
+  }
+
   public ClassFileTransformer setAccessFlags(Consumer<ClassAccessFlags> fn) {
     return addClassTransformer(
         new ClassTransformer() {
@@ -423,6 +440,50 @@
             });
   }
 
+  public ClassFileTransformer setPermittedSubclasses(
+      Class<?> clazz, Class<?>... permittedSubclasses) {
+    assert !Arrays.asList(permittedSubclasses).contains(clazz);
+    return setMinVersion(CfVm.JDK17)
+        .addClassTransformer(
+            new ClassTransformer() {
+
+              final String name = DescriptorUtils.getBinaryNameFromJavaType(clazz.getTypeName());
+
+              final List<String> permittedSubclassesNames =
+                  Arrays.stream(permittedSubclasses)
+                      .map(m -> DescriptorUtils.getBinaryNameFromJavaType(m.getTypeName()))
+                      .collect(Collectors.toList());
+              String className;
+
+              @Override
+              public void visit(
+                  int version,
+                  int access,
+                  String name,
+                  String signature,
+                  String superName,
+                  String[] interfaces) {
+                super.visit(version, access, name, signature, superName, interfaces);
+                className = name;
+              }
+
+              @Override
+              public void visitPermittedSubclass(String permittedSubclass) {
+                // Ignore/remove existing permitted subclasses.
+              }
+
+              @Override
+              public void visitEnd() {
+                if (className.equals(name)) {
+                  for (String permittedSubclass : permittedSubclassesNames) {
+                    super.visitPermittedSubclass(permittedSubclass);
+                  }
+                }
+                super.visitEnd();
+              }
+            });
+  }
+
   public ClassFileTransformer unsetAbstract() {
     return setAccessFlags(ClassAccessFlags::unsetAbstract);
   }
@@ -508,6 +569,11 @@
 
   private ClassFileTransformer setAccessFlags(
       MethodReference methodReference, Consumer<MethodAccessFlags> setter) {
+    return setAccessFlags(MethodPredicate.onReference(methodReference), setter);
+  }
+
+  public ClassFileTransformer setAccessFlags(
+      MethodPredicate predicate, Consumer<MethodAccessFlags> setter) {
     return addClassTransformer(
         new ClassTransformer() {
 
@@ -519,8 +585,7 @@
                     || name.equals(Constants.CLASS_INITIALIZER_NAME);
             MethodAccessFlags accessFlags =
                 MethodAccessFlags.fromCfAccessFlags(access, isConstructor);
-            if (name.equals(methodReference.getMethodName())
-                && descriptor.equals(methodReference.getMethodDescriptor())) {
+            if (predicate.test(access, name, descriptor, signature, exceptions)) {
               setter.accept(accessFlags);
             }
             return super.visitMethod(
@@ -541,6 +606,12 @@
       return (access, otherName, descriptor, signature, exceptions) -> name.equals(otherName);
     }
 
+    static MethodPredicate onReference(MethodReference reference) {
+      return (access, otherName, descriptor, signature, exceptions) ->
+          reference.getMethodName().equals(otherName)
+              && reference.getMethodDescriptor().equals(descriptor);
+    }
+
     static boolean testContext(MethodPredicate predicate, MethodContext context) {
       MethodReference reference = context.getReference();
       return predicate.test(
@@ -604,6 +675,31 @@
         });
   }
 
+  public ClassFileTransformer rewriteEnlosingAndNestAttributes(Function<String, String> rewrite) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visitInnerClass(String name, String outerName, String innerName, int access) {
+            super.visitInnerClass(rewrite.apply(name), rewrite.apply(outerName), innerName, access);
+          }
+
+          @Override
+          public void visitOuterClass(String owner, String name, String descriptor) {
+            super.visitOuterClass(rewrite.apply(owner), name, descriptor);
+          }
+
+          @Override
+          public void visitNestMember(String nestMember) {
+            super.visitNestMember(rewrite.apply(nestMember));
+          }
+
+          @Override
+          public void visitNestHost(String nestHost) {
+            super.visitNestHost(rewrite.apply(nestHost));
+          }
+        });
+  }
+
   public ClassFileTransformer rewriteEnclosingMethod(
       String newOwner, String newName, String newDescriptor) {
     return addClassTransformer(
@@ -629,6 +725,18 @@
         });
   }
 
+  public ClassFileTransformer removeMethodsCodeAndAnnotations(MethodPredicate predicate) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
+            return predicate.test(access, name, descriptor, signature, exceptions) ? null : mv;
+          }
+        });
+  }
+
   public ClassFileTransformer removeMethodsWithName(String nameToRemove) {
     return removeMethods(
         (access, name, descriptor, signature, exceptions) -> name.equals(nameToRemove));
@@ -869,7 +977,7 @@
 
           @Override
           public AnnotationVisitor visitAnnotationDefault() {
-            return new AnnotationVisitor(ASM9, super.visitAnnotationDefault()) {
+            return new AnnotationVisitor(ASM_VERSION, super.visitAnnotationDefault()) {
               @Override
               public void visit(String name, Object value) {
                 super.visit(name, value);
@@ -921,6 +1029,38 @@
           }
 
           @Override
+          public void visitInvokeDynamicInsn(
+              String name,
+              String descriptor,
+              Handle bootstrapMethodHandle,
+              Object... bootstrapMethodArguments) {
+            // This includes the minimal support so that simple lambda are correctly rewritten.
+            // This should be extended based on need if we want to rewrite more complex
+            // invoke-dynamic.
+            Object[] newBootArgs = new Object[bootstrapMethodArguments.length];
+            for (int i = 0; i < bootstrapMethodArguments.length; i++) {
+              Object arg = bootstrapMethodArguments[i];
+              if (arg instanceof Handle) {
+                Handle oldHandle = (Handle) arg;
+                String repl =
+                    replaceAll("L" + oldHandle.getOwner() + ";", oldDescriptor, newDescriptor);
+                String newOwner = repl.substring(1, repl.length() - 1);
+                Handle newHandle =
+                    new Handle(
+                        oldHandle.getTag(),
+                        newOwner,
+                        oldHandle.getName(),
+                        oldHandle.getDesc(),
+                        oldHandle.isInterface());
+                newBootArgs[i] = newHandle;
+              } else {
+                newBootArgs[i] = arg;
+              }
+            }
+            super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, newBootArgs);
+          }
+
+          @Override
           public void visitTypeInsn(int opcode, String type) {
             super.visitTypeInsn(opcode, rewriteASMInternalTypeName(type));
           }
@@ -945,7 +1085,7 @@
 
   private MethodVisitor redirectVisitInvokeDynamicInsn(
       MethodVisitor visitor, VisitInvokeDynamicInsnCallback callback) {
-    return new MethodVisitor(ASM7, visitor) {
+    return new MethodVisitor(ASM_VERSION, visitor) {
       @Override
       public void visitInvokeDynamicInsn(
           String name,
@@ -1036,7 +1176,7 @@
 
   private MethodVisitor redirectVisitFieldInsn(
       MethodVisitor visitor, VisitFieldInsnCallback callback) {
-    return new MethodVisitor(ASM7, visitor) {
+    return new MethodVisitor(ASM_VERSION, visitor) {
       @Override
       public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
         callback.visitFieldInsn(opcode, owner, name, descriptor);
@@ -1064,6 +1204,38 @@
         });
   }
 
+  public ClassFileTransformer setPredictiveLineNumbering() {
+    return setPredictiveLineNumbering(MethodPredicate.all());
+  }
+
+  public ClassFileTransformer setPredictiveLineNumbering(MethodPredicate predicate) {
+    return setPredictiveLineNumbering(predicate, 0);
+  }
+
+  public ClassFileTransformer setPredictiveLineNumbering(
+      MethodPredicate predicate, int startingLineNumber) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          private final Map<MethodReference, Integer> lines = new HashMap<>();
+
+          @Override
+          public void visitLineNumber(int line, Label start) {
+            if (MethodPredicate.testContext(predicate, getContext())) {
+              Integer nextLine =
+                  lines.getOrDefault(getContext().getReference(), startingLineNumber);
+              if (nextLine > 0) {
+                super.visitLineNumber(nextLine, start);
+              }
+              // Increment the actual line content by 100 so that each one is clearly distinct
+              // from a PC value for any of the methods.
+              lines.put(getContext().getReference(), nextLine + 100);
+            } else {
+              super.visitLineNumber(line, start);
+            }
+          }
+        });
+  }
+
   @FunctionalInterface
   private interface VisitMethodInsnCallback {
     void visitMethodInsn(
@@ -1072,7 +1244,7 @@
 
   private MethodVisitor redirectVisitMethodInsn(
       MethodVisitor visitor, VisitMethodInsnCallback callback) {
-    return new MethodVisitor(ASM7, visitor) {
+    return new MethodVisitor(ASM_VERSION, visitor) {
       @Override
       public void visitMethodInsn(
           int opcode, String owner, String name, String descriptor, boolean isInterface) {
@@ -1110,7 +1282,7 @@
 
   private MethodVisitor redirectVisitTypeInsn(
       MethodVisitor visitor, VisitTypeInsnCallback callback) {
-    return new MethodVisitor(ASM7, visitor) {
+    return new MethodVisitor(ASM_VERSION, visitor) {
       @Override
       public void visitTypeInsn(int opcode, String type) {
         callback.visitTypeInsn(opcode, type);
@@ -1141,7 +1313,7 @@
 
   private MethodVisitor redirectVisitTryCatchBlock(
       MethodVisitor visitor, VisitTryCatchBlockCallback callback) {
-    return new MethodVisitor(ASM7, visitor) {
+    return new MethodVisitor(ASM_VERSION, visitor) {
       @Override
       public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
         callback.visitTryCatchBlock(start, end, handler, type);
@@ -1175,7 +1347,7 @@
   }
 
   private MethodVisitor redirectVisitLdcInsn(MethodVisitor visitor, VisitLdcInsnCallback callback) {
-    return new MethodVisitor(ASM7, visitor) {
+    return new MethodVisitor(ASM_VERSION, visitor) {
       @Override
       public void visitLdcInsn(Object value) {
         callback.visitLdcInsn(value);
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java
index 885257e..172f334 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.transformers;
 
-import static org.objectweb.asm.Opcodes.ASM7;
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 
 import org.objectweb.asm.ClassVisitor;
 
@@ -14,7 +14,7 @@
  */
 public class ClassTransformer extends ClassVisitor {
   public ClassTransformer() {
-    super(ASM7, null);
+    super(ASM_VERSION, null);
   }
 
   // Package internals.
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 55b84f4..a790753 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -225,6 +225,11 @@
   }
 
   @Override
+  public List<TypeSubject> getFinalPermittedSubclassAttributes() {
+    throw new Unreachable("Cannot determine PermittedSubclasses attribute of an absent class");
+  }
+
+  @Override
   public KmClassSubject getKmClass() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index a4fb7b4..e3855b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -67,6 +67,11 @@
   }
 
   @Override
+  public boolean isNative() {
+    throw new Unreachable("Cannot determine if an absent method is native");
+  }
+
+  @Override
   public MethodAccessFlags getAccessFlags() {
     throw new Unreachable("Cannot get the access flags for an absent method");
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 36a856e..db4985c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -246,6 +246,8 @@
 
   public abstract List<TypeSubject> getFinalNestMembersAttribute();
 
+  public abstract List<TypeSubject> getFinalPermittedSubclassAttributes();
+
   public abstract KmClassSubject getKmClass();
 
   public abstract KmPackageSubject getKmPackage();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 44942ab..402ef93 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -51,6 +51,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -59,6 +60,8 @@
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 public class CodeInspector {
 
@@ -232,15 +235,26 @@
     }
   }
 
-  DexAnnotation findAnnotation(String name, DexAnnotationSet annotations) {
-    for (DexAnnotation annotation : annotations.annotations) {
-      DexType type = annotation.annotation.type;
-      String original = mapping == null ? type.toSourceString() : mapping.originalNameOf(type);
-      if (original.equals(name)) {
-        return annotation;
-      }
-    }
-    return null;
+  public DexAnnotation findAnnotation(DexAnnotationSet annotationSet, String name) {
+    return findAnnotation(
+        annotationSet,
+        annotation -> {
+          DexType type = annotation.annotation.type;
+          String original = mapping == null ? type.toSourceString() : mapping.originalNameOf(type);
+          return original.equals(name);
+        });
+  }
+
+  public DexAnnotation findAnnotation(
+      DexAnnotationSet annotationSet, Predicate<DexAnnotation> predicate) {
+    List<DexAnnotation> annotations = findAnnotations(annotationSet, predicate);
+    assert annotations.size() <= 1;
+    return annotations.isEmpty() ? null : annotations.get(0);
+  }
+
+  public List<DexAnnotation> findAnnotations(
+      DexAnnotationSet annotationSet, Predicate<DexAnnotation> predicate) {
+    return Arrays.stream(annotationSet.annotations).filter(predicate).collect(Collectors.toList());
   }
 
   public String getOriginalSignatureAttribute(
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 5b0e184..cb65261 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
 import com.android.tools.r8.kotlin.KotlinClassMetadataReader;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming;
@@ -370,7 +371,7 @@
     assert !name.endsWith("EnclosingClass")
         && !name.endsWith("EnclosingMethod")
         && !name.endsWith("InnerClass");
-    DexAnnotation annotation = codeInspector.findAnnotation(name, dexClass.annotations());
+    DexAnnotation annotation = codeInspector.findAnnotation(dexClass.annotations(), name);
     return annotation == null
         ? new AbsentAnnotationSubject()
         : new FoundAnnotationSubject(annotation, codeInspector);
@@ -504,6 +505,16 @@
   }
 
   @Override
+  public List<TypeSubject> getFinalPermittedSubclassAttributes() {
+    List<TypeSubject> result = new ArrayList<>();
+    for (PermittedSubclassAttribute permittedSubclassAttribute :
+        dexClass.getPermittedSubclassAttributes()) {
+      result.add(new TypeSubject(codeInspector, permittedSubclassAttribute.getPermittedSubclass()));
+    }
+    return result;
+  }
+
+  @Override
   public int hashCode() {
     int result = codeInspector.hashCode();
     result = 31 * result + dexClass.hashCode();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index c2daded..d16221e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -120,7 +120,7 @@
 
   @Override
   public AnnotationSubject annotation(String name) {
-    DexAnnotation annotation = codeInspector.findAnnotation(name, dexField.annotations());
+    DexAnnotation annotation = codeInspector.findAnnotation(dexField.annotations(), name);
     return annotation == null
         ? new AbsentAnnotationSubject()
         : new FoundAnnotationSubject(annotation, codeInspector);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 8a27919..533fa55 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -119,6 +119,11 @@
   }
 
   @Override
+  public boolean isNative() {
+    return dexMethod.getAccessFlags().isNative();
+  }
+
+  @Override
   public MethodAccessFlags getAccessFlags() {
     return dexMethod.getAccessFlags();
   }
@@ -372,7 +377,7 @@
 
   @Override
   public AnnotationSubject annotation(String name) {
-    DexAnnotation annotation = codeInspector.findAnnotation(name, dexMethod.annotations());
+    DexAnnotation annotation = codeInspector.findAnnotation(dexMethod.annotations(), name);
     return annotation == null
         ? new AbsentAnnotationSubject()
         : new FoundAnnotationSubject(annotation, codeInspector);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index db174e0..4a3d5f9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -428,7 +428,31 @@
             description.appendText("package-private");
           }
         } else {
-          description.appendText(" was absent");
+          description.appendText("absent");
+        }
+      }
+    };
+  }
+
+  public static <T extends MethodSubject> Matcher<T> isNative() {
+    return new TypeSafeMatcher<T>() {
+      @Override
+      public boolean matchesSafely(final T subject) {
+        return subject.isPresent() && subject.isNative();
+      }
+
+      @Override
+      public void describeTo(final Description description) {
+        description.appendText("native method");
+      }
+
+      @Override
+      public void describeMismatchSafely(final T subject, Description description) {
+        description.appendText("item ").appendValue(subject.getOriginalName()).appendText(" was ");
+        if (subject.isPresent()) {
+          description.appendText("not native");
+        } else {
+          description.appendText("absent");
         }
       }
     };
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index dda3e35..a083636 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -38,6 +38,8 @@
 
   public abstract boolean isVirtual();
 
+  public abstract boolean isNative();
+
   public FoundMethodSubject asFoundMethodSubject() {
     return null;
   }
diff --git a/third_party/android_jar/libcore_latest.tar.gz.sha1 b/third_party/android_jar/libcore_latest.tar.gz.sha1
new file mode 100644
index 0000000..c300639
--- /dev/null
+++ b/third_party/android_jar/libcore_latest.tar.gz.sha1
@@ -0,0 +1 @@
+f7570bec27977e786b637241778e6cfdadf25f7f
\ 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
new file mode 100644
index 0000000..e672542
--- /dev/null
+++ b/third_party/youtube/youtube.android_17.19.tar.gz.sha1
@@ -0,0 +1 @@
+474aefd92152017a7e0ad54020b95c6d5045a0bb
\ No newline at end of file
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index a4a010b..7f3932b 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -39,6 +39,10 @@
   parser.add_option('--quiet',
                     help='disable verbose logging',
                     default=False)
+  parser.add_option('--sign-before-align',
+                    help='Sign the apk before aligning',
+                    default=False,
+                    action='store_true')
   (options, args) = parser.parse_args()
   if len(args) != 1:
     parser.error('Expected <apk> argument, got: ' + ' '.join(args))
@@ -86,8 +90,9 @@
   return apk_utils.align(signed_apk, aligned_apk)
 
 def masseur(
-    apk, dex=None, resources=None, out=None, adb_options=None, keystore=None,
-    install=False, quiet=False, logging=True):
+    apk, dex=None, resources=None, out=None, adb_options=None,
+    sign_before_align=False, keystore=None, install=False, quiet=False,
+    logging=True):
   if not out:
     out = os.path.basename(apk)
   if not keystore:
@@ -101,11 +106,18 @@
           'Signing original APK without modifying dex files', quiet=quiet)
       processed_apk = os.path.join(temp, 'processed.apk')
       shutil.copyfile(apk, processed_apk)
-    signed_apk = sign(
-        processed_apk, keystore, temp, quiet=quiet, logging=logging)
-    aligned_apk = align(signed_apk, temp, quiet=quiet, logging=logging)
-    utils.Print('Writing result to {}'.format(out), quiet=quiet)
-    shutil.copyfile(aligned_apk, out)
+    if sign_before_align:
+      signed_apk = sign(
+          processed_apk, keystore, temp, quiet=quiet, logging=logging)
+      aligned_apk = align(signed_apk, temp, quiet=quiet, logging=logging)
+      utils.Print('Writing result to {}'.format(out), quiet=quiet)
+      shutil.copyfile(aligned_apk, out)
+    else:
+      aligned_apk = align(processed_apk, temp, quiet=quiet, logging=logging)
+      signed_apk = sign(
+          aligned_apk, keystore, temp, quiet=quiet, logging=logging)
+      utils.Print('Writing result to {}'.format(out), quiet=quiet)
+      shutil.copyfile(signed_apk, out)
     if install:
       adb_cmd = ['adb']
       if adb_options:
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index 86d3b0f..906284e 100755
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -90,6 +90,7 @@
     '--ks-pass', 'pass:' + password,
     '--min-sdk-version', '19',
     '--out', signed_apk,
+    '--v2-signing-enabled',
     unsigned_apk
   ]
   utils.RunCmd(cmd, quiet=quiet, logging=logging)
diff --git a/tools/google-java-format-diff.py b/tools/google-java-format-diff.py
new file mode 100755
index 0000000..c9c3dc3
--- /dev/null
+++ b/tools/google-java-format-diff.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+#
+#===- google-java-format-diff.py - google-java-format Diff Reformatter -----===#
+#
+#                     The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+
+"""
+google-java-format Diff Reformatter
+============================
+
+This script reads input from a unified diff and reformats all the changed
+lines. This is useful to reformat all the lines touched by a specific patch.
+Example usage for git/svn users:
+
+  git diff -U0 HEAD^ | google-java-format-diff.py -p1 -i
+  svn diff --diff-cmd=diff -x-U0 | google-java-format-diff.py -i
+
+For perforce users:
+
+  P4DIFF="git --no-pager diff --no-index" p4 diff | ./google-java-format-diff.py -i -p7
+
+"""
+
+import argparse
+import difflib
+import re
+import string
+import subprocess
+import io
+import os
+import sys
+from shutil import which
+
+def main():
+  parser = argparse.ArgumentParser(description=
+                                   'Reformat changed lines in diff. Without -i '
+                                   'option just output the diff that would be '
+                                   'introduced.')
+  parser.add_argument('-i', action='store_true', default=False,
+                      help='apply edits to files instead of displaying a diff')
+
+  parser.add_argument('-p', metavar='NUM', default=0,
+                      help='strip the smallest prefix containing P slashes')
+  parser.add_argument('-regex', metavar='PATTERN', default=None,
+                      help='custom pattern selecting file paths to reformat '
+                      '(case sensitive, overrides -iregex)')
+  parser.add_argument('-iregex', metavar='PATTERN', default=r'.*\.java',
+                      help='custom pattern selecting file paths to reformat '
+                      '(case insensitive, overridden by -regex)')
+  parser.add_argument('-v', '--verbose', action='store_true',
+                      help='be more verbose, ineffective without -i')
+  parser.add_argument('-a', '--aosp', action='store_true',
+                      help='use AOSP style instead of Google Style (4-space indentation)')
+  parser.add_argument('--skip-sorting-imports', action='store_true',
+                      help='do not fix the import order')
+  parser.add_argument('--skip-removing-unused-imports', action='store_true',
+                      help='do not remove ununsed imports')
+  parser.add_argument(
+      '--skip-javadoc-formatting',
+      action='store_true',
+      default=False,
+      help='do not reformat javadoc')
+  parser.add_argument('-b', '--binary', help='path to google-java-format binary')
+  parser.add_argument('--google-java-format-jar', metavar='ABSOLUTE_PATH', default=None,
+                      help='use a custom google-java-format jar')
+
+  args = parser.parse_args()
+
+  # Extract changed lines for each file.
+  filename = None
+  lines_by_file = {}
+
+  for line in sys.stdin:
+    match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
+    if match:
+      filename = match.group(2)
+    if filename == None:
+      continue
+
+    if args.regex is not None:
+      if not re.match('^%s$' % args.regex, filename):
+        continue
+    else:
+      if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
+        continue
+
+    match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
+    if match:
+      start_line = int(match.group(1))
+      line_count = 1
+      if match.group(3):
+        line_count = int(match.group(3))
+      if line_count == 0:
+        continue
+      end_line = start_line + line_count - 1;
+      lines_by_file.setdefault(filename, []).extend(
+          ['-lines', str(start_line) + ':' + str(end_line)])
+
+  if args.binary:
+    base_command = [args.binary]
+  elif args.google_java_format_jar:
+    base_command = [
+       os.path.join(
+        'third_party', 'openjdk', 'jdk-17', 'linux', 'bin', 'java'),
+        '-jar',
+        '--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
+        '--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED',
+        '--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
+        '--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
+        '--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
+        '--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
+        args.google_java_format_jar]
+  else:
+    binary = which('google-java-format') or '/usr/bin/google-java-format'
+    base_command = [binary]
+
+  # Reformat files containing changes in place.
+  for filename, lines in lines_by_file.items():
+    if args.i and args.verbose:
+      print('Formatting', filename)
+    command = base_command[:]
+    if args.i:
+      command.append('-i')
+    if args.aosp:
+      command.append('--aosp')
+    if args.skip_sorting_imports:
+      command.append('--skip-sorting-imports')
+    if args.skip_removing_unused_imports:
+      command.append('--skip-removing-unused-imports')
+    if args.skip_javadoc_formatting:
+      command.append('--skip-javadoc-formatting')
+    command.extend(lines)
+    command.append(filename)
+    p = subprocess.Popen(command, stdout=subprocess.PIPE,
+                         stderr=None, stdin=subprocess.PIPE)
+    stdout, stderr = p.communicate()
+    if p.returncode != 0:
+      sys.exit(p.returncode);
+
+    if not args.i:
+      with open(filename) as f:
+        code = f.readlines()
+      formatted_code = io.StringIO(stdout.decode('utf-8')).readlines()
+      diff = difflib.unified_diff(code, formatted_code,
+                                  filename, filename,
+                                  '(before formatting)', '(after formatting)')
+      diff_string = ''.join(diff)
+      if len(diff_string) > 0:
+        sys.stdout.write(diff_string)
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/historic_run.py b/tools/historic_run.py
index eaaadd5..75819bb 100755
--- a/tools/historic_run.py
+++ b/tools/historic_run.py
@@ -155,7 +155,7 @@
   output_path = options.output or 'build'
   time_commit = '%s_%s' % (commit.timestamp, commit.git_hash)
   time_commit_path = os.path.join(output_path, time_commit)
-  print ' '.join(cmd)
+  print(' '.join(cmd))
   if not options.dry_run:
     if not os.path.exists(time_commit_path):
       os.makedirs(time_commit_path)
@@ -170,7 +170,7 @@
           timeout -= 1
         if process.poll() is None:
           process.kill()
-          print "Task timed out"
+          print("Task timed out")
           stderr.write("timeout\n")
   print('Wrote outputs to: %s' % time_commit_path)
 
diff --git a/tools/r8_get.py b/tools/r8_get.py
new file mode 100755
index 0000000..cc11e90
--- /dev/null
+++ b/tools/r8_get.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022, 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.
+
+import sys
+import argparse
+import compiledump
+
+def parse_arguments():
+  parser = argparse.ArgumentParser(
+      description = 'Helper to fetch r8.jar from cloudstorage.')
+  parser.add_argument(
+      '-v',
+      '--version',
+      help='Version or commit-hash to download '
+           '(e.g., 3.3.50 or 33ae86d80351efc4d632452331d06cb97e42f2a7).',
+      required=True)
+  parser.add_argument(
+      '--outdir',
+      help='Output directory to place the r8.jar in (default cwd).',
+      default=None)
+  return parser.parse_args()
+
+def main():
+  args = parse_arguments()
+  outdir = args.outdir if args.outdir else ''
+  print(compiledump.download_distribution(args.version, True, outdir))
+  return 0
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 50dba8d..8c72939 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -606,6 +606,9 @@
       extra_args.append('-Dcom.android.tools.r8.allowTypeErrors=1')
     extra_args.append(
         '-Dcom.android.tools.r8.disallowClassInlinerGracefulExit=1')
+    if 'system-properties' in values:
+      for system_property in values['system-properties']:
+        extra_args.append(system_property)
 
   if options.debug_agent:
     if not options.compiler_build == 'full':
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py
old mode 100644
new mode 100755
index b5e2af2..56ad643
--- a/tools/startup/adb_utils.py
+++ b/tools/startup/adb_utils.py
@@ -3,13 +3,15 @@
 # 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.
 
-from enum import Enum
+import argparse
 import os
 import subprocess
 import sys
 import threading
 import time
 
+from enum import Enum
+
 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 
 import utils
@@ -351,3 +353,37 @@
     issue_key_event('KEYCODE_MENU', device_id)
     screen_state = get_screen_state(device_id)
   assert screen_state.is_on_and_unlocked(), 'was %s' % screen_state
+
+def parse_options(argv):
+  result = argparse.ArgumentParser(description='Run adb utils.')
+  result.add_argument('--device-id',
+                      help='Device id (e.g., emulator-5554).')
+  result.add_argument('--device-pin',
+                      help='Device pin code (e.g., 1234)')
+  result.add_argument('--ensure-screen-off',
+                      help='Ensure screen off',
+                      action='store_true',
+                      default=False)
+  result.add_argument('--get-screen-state',
+                      help='Get screen state',
+                      action='store_true',
+                      default=False)
+  result.add_argument('--unlock',
+                      help='Unlock device',
+                      action='store_true',
+                      default=False)
+  options, args = result.parse_known_args(argv)
+  return options, args
+
+def main(argv):
+  (options, args) = parse_options(argv)
+  if options.ensure_screen_off:
+    ensure_screen_off(options.device_id)
+  elif options.get_screen_state:
+    print(get_screen_state(options.device_id))
+  elif options.unlock:
+    unlock(options.device_id, options.device_pin)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/test.py b/tools/test.py
index 27c07de..570da44 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -134,6 +134,9 @@
   result.add_option('--no-r8lib', '--no_r8lib',
       default=False, action='store_true',
       help='Run the tests on R8 full with relocated dependencies.')
+  result.add_option('--no-arttests', '--no_arttests',
+      default=False, action='store_true',
+      help='Do not run the art tests.')
   result.add_option('--r8lib-no-deps', '--r8lib_no_deps',
       default=False, action='store_true',
       help='Run the tests on r8lib without relocated dependencies.')
@@ -345,6 +348,8 @@
     gradle_args.append('-Pdesugar_jdk_json_dir=' + desugar_jdk_json_dir)
   if desugar_jdk_libs:
     gradle_args.append('-Pdesugar_jdk_libs=' + desugar_jdk_libs)
+  if options.no_arttests:
+    gradle_args.append('-Pno_arttests=true')
   if options.reset_testing_state:
     gradle_args.append('-Ptesting-state')
     gradle_args.append('-Preset-testing-state')
diff --git a/tools/upload_to_x20.py b/tools/upload_to_x20.py
index 3a1ef77..e6c293d 100755
--- a/tools/upload_to_x20.py
+++ b/tools/upload_to_x20.py
@@ -21,7 +21,7 @@
   return optparse.OptionParser().parse_args()
 
 def uploadFile(filename, dest):
-  print 'Uploading to %s' % dest
+  print('Uploading to %s' % dest)
   shutil.copyfile(filename, dest)
   subprocess.check_call(['chmod', '664', dest])
 
@@ -29,9 +29,9 @@
   (options, args) = parse_options()
   assert len(args) == 1
   name = args[0]
-  print 'Creating archive for %s' % name
+  print('Creating archive for %s' % name)
   if not name in os.listdir('.'):
-    print 'You must be standing directly below the directory you are uploading'
+    print('You must be standing directly below the directory you are uploading')
     return 1
   filename = utils.create_archive(name)
   sha1 = utils.get_sha1(filename)
@@ -40,7 +40,7 @@
   sha1_file = '%s.sha1' % filename
   with open(sha1_file, 'w') as output:
     output.write(sha1)
-  print 'Sha (%s) written to: %s' % (sha1, sha1_file)
+  print('Sha (%s) written to: %s' % (sha1, sha1_file))
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/utils.py b/tools/utils.py
index 85fda69..363cc18 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -159,8 +159,18 @@
       ANDROID_HOME_ENVIROMENT_NAME, os.path.join(USER_HOME, 'Android', 'Sdk'))
 
 def getAndroidBuildTools():
-  version = os.environ.get(ANDROID_TOOLS_VERSION_ENVIRONMENT_NAME, '28.0.3')
-  return os.path.join(getAndroidHome(), 'build-tools', version)
+  if ANDROID_TOOLS_VERSION_ENVIRONMENT_NAME in os.environ:
+    version = os.environ.get(ANDROID_TOOLS_VERSION_ENVIRONMENT_NAME)
+    build_tools_dir = os.path.join(getAndroidHome(), 'build-tools', version)
+    assert os.path.exists(build_tools_dir)
+    return build_tools_dir
+  else:
+    versions = ['30.0.3', '30.0.2', '30.0.1', '30.0.0']
+    for version in versions:
+      build_tools_dir = os.path.join(getAndroidHome(), 'build-tools', version)
+      if os.path.exists(build_tools_dir):
+        return build_tools_dir
+  raise Exception('Unable to find Android build-tools')
 
 def is_python3():
   return sys.version_info.major == 3
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index 8e1349b..cd47f66 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -7,6 +7,8 @@
 
 ANDROID_H_MR2_API = '13'
 ANDROID_L_API = '21'
+ANDROID_M_API = '23'
+
 BASE = os.path.join(utils.THIRD_PARTY, 'youtube')
 
 V15_33_BASE = os.path.join(BASE, 'youtube.android_15.33')
@@ -15,7 +17,10 @@
 V16_20_BASE = os.path.join(BASE, 'youtube.android_16.20')
 V16_20_PREFIX = os.path.join(V16_20_BASE, 'YouTubeRelease')
 
-LATEST_VERSION = '16.20'
+V17_19_BASE = os.path.join(BASE, 'youtube.android_17.19')
+V17_19_PREFIX = os.path.join(V17_19_BASE, 'YouTubeRelease')
+
+LATEST_VERSION = '17.19'
 
 VERSIONS = {
   '15.33': {
@@ -80,6 +85,37 @@
       'min-api' : ANDROID_L_API,
     }
   },
+  '17.19': {
+    'deploy' : {
+      'sanitize_libraries': False,
+      'inputs': ['%s_deploy.jar' % V17_19_PREFIX],
+      'libraries' : [
+          os.path.join(
+              V17_19_BASE,
+              'legacy_YouTubeRelease_combined_library_jars_filtered.jar')],
+      'pgconf': [
+          '%s_proguard.config' % V17_19_PREFIX,
+          '%s_proguard_extra.config' % V17_19_PREFIX,
+          '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY,
+          utils.IGNORE_WARNINGS_RULES],
+      'min-api' : ANDROID_M_API,
+      'system-properties': [
+          # TODO(b/235169948): Reenable -checkenumunboxed.
+          # '-Dcom.android.tools.r8.experimental.enablecheckenumunboxed=1',
+          '-Dcom.android.tools.r8.experimental.enableconvertchecknotnull=1'],
+      'android_java8_libs': {
+        'config': '%s/desugar_jdk_libs/full_desugar_jdk_libs.json' % V17_19_BASE,
+        # Intentionally not adding desugar_jdk_libs_configuration.jar since it
+        # is part of jdk_libs_to_desugar.jar in YouTube 17.19.
+        'program': ['%s/desugar_jdk_libs/jdk_libs_to_desugar.jar' % V17_19_BASE],
+        'library': '%s/android_jar/lib-v33/android.jar' % utils.THIRD_PARTY,
+        'pgconf': [
+          '%s/desugar_jdk_libs/base.pgcfg' % V17_19_BASE,
+          '%s/desugar_jdk_libs/minify_desugar_jdk_libs.pgcfg' % V17_19_BASE
+        ]
+      }
+    },
+  },
 }
 
 def GetLatestVersion():