Merge commit 'd541560cbaab87e2feb1f173bb39ab48f8e09616' into dev-release

Change-Id: I8bb4a071c291d9c639a79225a842f546cedcad6b
diff --git a/scripts/add-android-jar.sh b/scripts/add-android-jar.sh
index e2fe239..3f575b4 100755
--- a/scripts/add-android-jar.sh
+++ b/scripts/add-android-jar.sh
@@ -16,7 +16,7 @@
 SDK_HOME=$HOME/Android/Sdk
 
 # Modify these to match the SDK android.jar to add.
-SDK_DIR_NAME=android-VanillaIceCream
+SDK_DIR_NAME=android-35
 SDK_VERSION=35
 
 SDK_DIR=$SDK_HOME/platforms/$SDK_DIR_NAME
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
index 47fdb1d..9413ca3 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
@@ -34,6 +34,7 @@
    * <ul>
    *   <li>constant
    *   <li>classNamePattern
+   *   <li>instanceOfPattern
    * </ul>
    */
   String name() default "";
@@ -48,6 +49,7 @@
    * <ul>
    *   <li>name
    *   <li>classNamePattern
+   *   <li>instanceOfPattern
    * </ul>
    */
   Class<?> constant() default Object.class;
@@ -60,7 +62,23 @@
    * <ul>
    *   <li>name
    *   <li>constant
+   *   <li>instanceOfPattern
    * </ul>
    */
   ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = "");
+
+  /**
+   * Define the instance-of with a pattern.
+   *
+   * <p>Mutually exclusive with the following other properties defining type-pattern:
+   *
+   * <ul>
+   *   <li>name
+   *   <li>constant
+   *   <li>classNamePattern
+   * </ul>
+   *
+   * @return The pattern that defines what instance-of the class must be.
+   */
+  InstanceOfPattern instanceOfPattern() default @InstanceOfPattern();
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
index 538c8ca..e0ef6b5 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -69,7 +69,11 @@
         arrayTypePattern -> {
           throw parsingContext.error("Invalid use of array type where class type was expected");
         },
-        classNamePattern -> classNamePattern);
+        classNamePattern -> classNamePattern,
+        instanceOfPattern -> {
+          throw parsingContext.error(
+              "Invalid use of instance of type where class type was expected");
+        });
   }
 
   @Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index c1f8c13..2645b35 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -466,6 +466,9 @@
                 },
                 clazz -> {
                   writeClassNamePattern(clazz, TypePattern.classNamePattern, v);
+                },
+                instanceOf -> {
+                  writeInstanceOfPattern(instanceOf, v);
                 }));
   }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
index a0c0afc..f81610e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.keepanno.asm;
 
 import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
+import com.android.tools.r8.keepanno.asm.InstanceOfParser.InstanceOfProperties;
 import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.TypePattern;
 import com.android.tools.r8.keepanno.ast.KeepTypePattern;
@@ -24,7 +25,8 @@
     TYPE_PATTERN,
     TYPE_NAME,
     TYPE_CONSTANT,
-    CLASS_NAME_PATTERN
+    CLASS_NAME_PATTERN,
+    INSTANCE_OF_PATTERN
   }
 
   @Override
@@ -56,6 +58,7 @@
           typeParser.setProperty(TypePattern.name, TypeProperty.TYPE_NAME);
           typeParser.setProperty(TypePattern.constant, TypeProperty.TYPE_CONSTANT);
           typeParser.setProperty(TypePattern.classNamePattern, TypeProperty.CLASS_NAME_PATTERN);
+          typeParser.setProperty(TypePattern.instanceOfPattern, TypeProperty.INSTANCE_OF_PATTERN);
           return new ParserVisitor(
               context,
               typeParser,
@@ -70,6 +73,15 @@
               descriptor,
               value -> setValue.accept(KeepTypePattern.fromClass(value)));
         }
+      case INSTANCE_OF_PATTERN:
+        {
+          InstanceOfParser parser = new InstanceOfParser(getParsingContext());
+          return parser.tryPropertyAnnotation(
+              InstanceOfProperties.PATTERN,
+              name,
+              descriptor,
+              value -> setValue.accept(KeepTypePattern.fromInstanceOf(value)));
+        }
       default:
         return null;
     }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index 9b0ae29..77643cd 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -237,6 +237,7 @@
     public static final String name = "name";
     public static final String constant = "constant";
     public static final String classNamePattern = "classNamePattern";
+    public static final String instanceOfPattern = "instanceOfPattern";
   }
 
   public static final class ClassNamePattern {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
index 6194fb3..5981336 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
@@ -47,11 +47,14 @@
             () -> {
               throw new KeepEdgeException("No descriptor exists for 'any primitive' array");
             },
-            primitive -> primitive.getDescriptor(),
+            KeepPrimitiveTypePattern::getDescriptor,
             array -> {
               throw new KeepEdgeException("Unexpected nested array");
             },
-            clazz -> clazz.getExactDescriptor());
+            KeepQualifiedClassNamePattern::getExactDescriptor,
+            instanceOf -> {
+              throw new KeepEdgeException("No descriptor exists for instanceOf array");
+            });
   }
 
   @Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
index a0edf23..7e3c088 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
@@ -28,6 +28,10 @@
     return new ClassType(type);
   }
 
+  public static KeepTypePattern fromInstanceOf(KeepInstanceOfPattern pattern) {
+    return new KeepInstanceOf(pattern);
+  }
+
   public static KeepTypePattern fromDescriptor(String typeDescriptor) {
     char c = typeDescriptor.charAt(0);
     if (c == 'L') {
@@ -56,18 +60,21 @@
       Supplier<T> onAny,
       Function<KeepPrimitiveTypePattern, T> onPrimitive,
       Function<KeepArrayTypePattern, T> onArray,
-      Function<KeepQualifiedClassNamePattern, T> onClass);
+      Function<KeepQualifiedClassNamePattern, T> onClass,
+      Function<KeepInstanceOfPattern, T> onInstanceOf);
 
   public final void match(
       Runnable onAny,
       Consumer<KeepPrimitiveTypePattern> onPrimitive,
       Consumer<KeepArrayTypePattern> onArray,
-      Consumer<KeepQualifiedClassNamePattern> onClass) {
+      Consumer<KeepQualifiedClassNamePattern> onClass,
+      Consumer<KeepInstanceOfPattern> onInstanceOf) {
     apply(
         AstUtils.toVoidSupplier(onAny),
         AstUtils.toVoidFunction(onPrimitive),
         AstUtils.toVoidFunction(onArray),
-        AstUtils.toVoidFunction(onClass));
+        AstUtils.toVoidFunction(onClass),
+        AstUtils.toVoidFunction(onInstanceOf));
   }
 
   public boolean isAny() {
@@ -87,7 +94,8 @@
         Supplier<T> onAny,
         Function<KeepPrimitiveTypePattern, T> onPrimitive,
         Function<KeepArrayTypePattern, T> onArray,
-        Function<KeepQualifiedClassNamePattern, T> onClass) {
+        Function<KeepQualifiedClassNamePattern, T> onClass,
+        Function<KeepInstanceOfPattern, T> onInstanceOf) {
       return onAny.get();
     }
 
@@ -152,7 +160,8 @@
         Supplier<T> onAny,
         Function<KeepPrimitiveTypePattern, T> onPrimitive,
         Function<KeepArrayTypePattern, T> onArray,
-        Function<KeepQualifiedClassNamePattern, T> onClass) {
+        Function<KeepQualifiedClassNamePattern, T> onClass,
+        Function<KeepInstanceOfPattern, T> onInstanceOf) {
       return onPrimitive.apply(type);
     }
   }
@@ -169,7 +178,8 @@
         Supplier<T> onAny,
         Function<KeepPrimitiveTypePattern, T> onPrimitive,
         Function<KeepArrayTypePattern, T> onArray,
-        Function<KeepQualifiedClassNamePattern, T> onClass) {
+        Function<KeepQualifiedClassNamePattern, T> onClass,
+        Function<KeepInstanceOfPattern, T> onInstanceOf) {
       return onClass.apply(type);
     }
 
@@ -208,7 +218,8 @@
         Supplier<T> onAny,
         Function<KeepPrimitiveTypePattern, T> onPrimitive,
         Function<KeepArrayTypePattern, T> onArray,
-        Function<KeepQualifiedClassNamePattern, T> onClass) {
+        Function<KeepQualifiedClassNamePattern, T> onClass,
+        Function<KeepInstanceOfPattern, T> onInstanceOf) {
       return onArray.apply(type);
     }
 
@@ -234,4 +245,22 @@
       return type.toString();
     }
   }
+
+  private static class KeepInstanceOf extends KeepTypePattern {
+    private final KeepInstanceOfPattern instanceOf;
+
+    private KeepInstanceOf(KeepInstanceOfPattern instanceOf) {
+      this.instanceOf = instanceOf;
+    }
+
+    @Override
+    public <T> T apply(
+        Supplier<T> onAny,
+        Function<KeepPrimitiveTypePattern, T> onPrimitive,
+        Function<KeepArrayTypePattern, T> onArray,
+        Function<KeepQualifiedClassNamePattern, T> onClass,
+        Function<KeepInstanceOfPattern, T> onInstanceOf) {
+      return onInstanceOf.apply(instanceOf);
+    }
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 12d2caa..38a768f 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -221,7 +221,13 @@
         printer::appendTripleStar,
         primitivePattern -> printPrimitiveType(printer, primitivePattern),
         arrayTypePattern -> printArrayType(printer, arrayTypePattern),
-        classTypePattern -> printClassName(classTypePattern, printer));
+        classTypePattern -> printClassName(classTypePattern, printer),
+        instanceOfPattern -> printInstanceOf(instanceOfPattern, printer));
+  }
+
+  private static RulePrinter printInstanceOf(
+      KeepInstanceOfPattern instanceOfPattern, RulePrinter printer) {
+    throw new Unimplemented();
   }
 
   private static RulePrinter printPrimitiveType(
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
index af5a909..e4222dd 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
@@ -16,16 +16,39 @@
 public class AndroidApiLevelDatabaseHelper {
 
   public static Set<String> notModeledTypes() {
-    // The below types are known not to be modeled by any api-versions.
+    // The types below are known not to be modeled by any api-versions.
     Set<String> notModeledTypes = new HashSet<>();
     notModeledTypes.add("androidx.annotation.RecentlyNullable");
     notModeledTypes.add("androidx.annotation.RecentlyNonNull");
     notModeledTypes.add("android.annotation.Nullable");
     notModeledTypes.add("android.annotation.NonNull");
     notModeledTypes.add("android.annotation.FlaggedApi");
+    notModeledTypes.add("android.adservices.customaudience.PartialCustomAudience");
+    notModeledTypes.add("android.adservices.customaudience.PartialCustomAudience$Builder");
+    notModeledTypes.add(
+        "android.adservices.customaudience.ScheduleCustomAudienceUpdateRequest$Builder");
+    notModeledTypes.add("android.adservices.customaudience.ScheduleCustomAudienceUpdateRequest");
     return notModeledTypes;
   }
 
+  public static Set<String> notModeledFields() {
+    // The fields below are known not to be modeled by any api-versions.
+    Set<String> notModeledFields = new HashSet<>();
+    notModeledFields.add("int android.app.appsearch.AppSearchResult.RESULT_DENIED");
+    notModeledFields.add("int android.app.appsearch.AppSearchResult.RESULT_RATE_LIMITED");
+    return notModeledFields;
+  }
+
+  public static Set<String> notModeledMethods() {
+    // The methods below are known not to be modeled by any api-versions.
+    Set<String> notModelledMethods = new HashSet<>();
+    notModelledMethods.add(
+        "void android.adservices.customaudience.CustomAudienceManager.scheduleCustomAudienceUpdate(android.adservices.customaudience.ScheduleCustomAudienceUpdateRequest,"
+            + " java.util.concurrent.Executor,"
+            + " android.adservices.common.AdServicesOutcomeReceiver)");
+    return notModelledMethods;
+  }
+
   static void visitAdditionalKnownApiReferences(
       DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
     addStringBuilderAndBufferMethods(factory, apiLevelConsumer);
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
index a08de1f..fe4d746 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
@@ -15,16 +15,17 @@
 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;
 import com.android.tools.r8.utils.ThrowingCharIterator;
 import com.android.tools.r8.utils.ThrowingFunction;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.UTFDataFormatException;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class AndroidApiLevelHashingDatabaseImpl implements AndroidApiLevelDatabase {
@@ -36,6 +37,7 @@
   private static final byte[] NON_EXISTING_DESCRIPTOR = new byte[0];
 
   private final List<DexString> androidApiExtensionPackages;
+  private final Set<DexType> androidApiExtensionClasses;
 
   public static byte[] getNonExistingDescriptor() {
     return NON_EXISTING_DESCRIPTOR;
@@ -165,22 +167,35 @@
               predefinedApiReference.getReference(),
               Optional.of(predefinedApiReference.getApiLevel()));
         });
-    ImmutableList.Builder<DexString> androidApiExtensionPackagesBuilder =
-        new ImmutableList.Builder<>();
-    if (options.apiModelingOptions().androidApiExtensionPackages != null) {
-      StringUtils.split(options.apiModelingOptions().androidApiExtensionPackages, ',')
-          .forEach(
-              pkg -> {
-                androidApiExtensionPackagesBuilder.add(
-                    options.itemFactory.createString(
-                        "L"
-                            + pkg.replace(
-                                DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
-                                DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
-                            + "/"));
-              });
+
+    // Register classes in the extension libraries.
+    {
+      ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+      options
+          .apiModelingOptions()
+          .forEachAndroidApiExtensionClassDescriptor(
+              descriptor -> builder.add(options.itemFactory.createType(descriptor)));
+      this.androidApiExtensionClasses = builder.build();
     }
-    this.androidApiExtensionPackages = androidApiExtensionPackagesBuilder.build();
+    // Register packages for extension libraries.
+    // TODO(b/326252366): Remove support for  list of extension packages in favour of only
+    //  supporting passing extension libraries as JAR files.
+    {
+      ImmutableList.Builder<DexString> builder = ImmutableList.builder();
+      options
+          .apiModelingOptions()
+          .forEachAndroidApiExtensionPackage(
+              pkg ->
+                  builder.add(
+                      options.itemFactory.createString(
+                          "L"
+                              + pkg.replace(
+                                  DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+                                  DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+                              + "/")));
+      this.androidApiExtensionPackages = builder.build();
+    }
+
     assert predefinedApiTypeLookup.stream()
         .allMatch(added -> added.getApiLevel().isEqualTo(lookupApiLevel(added.getReference())));
   }
@@ -225,6 +240,9 @@
   private AndroidApiLevel lookupApiLevel(DexReference reference) {
     // TODO(b/326252366): Assigning all extension items the same "fake" API level results in API
     //  outlines becoming mergable across extensions, which should be prevented.
+    if (androidApiExtensionClasses.contains(reference.getContextType())) {
+      return AndroidApiLevel.EXTENSION;
+    }
     for (int i = 0; i < androidApiExtensionPackages.size(); i++) {
       DexString descriptor = reference.getContextType().getDescriptor();
       DexString extensionPackage = androidApiExtensionPackages.get(i);
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 54dc05d..21b8de8 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -81,14 +81,27 @@
   }
 
   public boolean canBeConvertedToAbstractMethod(AppView<AppInfoWithLiveness> appView) {
-    return (appView.options().canUseAbstractMethodOnNonAbstractClass()
+    if (!(appView.options().canUseAbstractMethodOnNonAbstractClass()
             || getHolder().isAbstract()
             || getHolder().isInterface())
-        && !getAccessFlags().isNative()
-        && !getAccessFlags().isPrivate()
-        && !getAccessFlags().isStatic()
-        && !getDefinition().isInstanceInitializer()
-        && !appView.appInfo().isFailedMethodResolutionTarget(getReference());
+        || getAccessFlags().isNative()
+        || getAccessFlags().isPrivate()
+        || getAccessFlags().isStatic()
+        || getDefinition().isInstanceInitializer()) {
+      return false;
+    }
+    // If the method has a failed resolution, then keep the method as non-abstract to preserve
+    // runtime errors, except when interface method desugaring is required.
+    if (appView.appInfo().isFailedMethodResolutionTarget(getReference())) {
+      boolean mustBeConvertedToAbstractMethod =
+          !appView.options().canUseDefaultAndStaticInterfaceMethods()
+              && getHolder().isInterface()
+              && getAccessFlags().belongsToVirtualPool();
+      if (!mustBeConvertedToAbstractMethod) {
+        return false;
+      }
+    }
+    return true;
   }
 
   public void convertToAbstractOrThrowNullMethod(AppView<AppInfoWithLiveness> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
index 77605d8..fd7a46d 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
@@ -6,6 +6,7 @@
 
 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.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -135,11 +136,27 @@
     if (previous.hasReboundReference()) {
       // Rewrite the rebound reference and then "fixup" the non-rebound reference.
       DexField rewrittenReboundReference = previous.getRewrittenReboundReference(fieldMap);
-      DexField rewrittenNonReboundReference =
-          previous.getReference() == previous.getReboundReference()
-              ? rewrittenReboundReference
-              : rewrittenReboundReference.withHolder(
-                  getNextClassType(previous.getReference().getHolderType()), dexItemFactory());
+      DexField rewrittenNonReboundReference;
+      if (previous.getReference().isIdenticalTo(previous.getReboundReference())) {
+        rewrittenNonReboundReference = rewrittenReboundReference;
+      } else {
+        // Compute the new holder by mapping the original symbolic holder through the lens.
+        DexType originalHolder = previous.getReference().getHolderType();
+        DexType rewrittenHolder = getNextClassType(originalHolder);
+        rewrittenNonReboundReference =
+            rewrittenReboundReference.withHolder(rewrittenHolder, dexItemFactory());
+        // When vertical class merging preserve non-rebound method references when we can hit a
+        // collision due to field shadowing (b/348202700).
+        if (isVerticalClassMergerLens() && rewrittenHolder.isNotIdenticalTo(originalHolder)) {
+          DexClassAndField collision = appView.definitionFor(rewrittenNonReboundReference);
+          if (collision != null) {
+            assert !asVerticalClassMergerLens().hasBeenMerged(collision.getHolder().getSuperType());
+            rewrittenNonReboundReference =
+                rewrittenReboundReference.withHolder(
+                    collision.getHolder().getSuperType(), dexItemFactory());
+          }
+        }
+      }
       return FieldLookupResult.builder(this)
           .setReboundReference(rewrittenReboundReference)
           .setReference(rewrittenNonReboundReference)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 48241f6..54e7d5c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import static com.android.tools.r8.ir.conversion.IRBuilder.EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET;
+import static com.android.tools.r8.ir.conversion.IRBuilder.EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET;
 import static it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMaps.emptyMap;
 
 import com.android.tools.r8.cf.code.CfFrame;
@@ -59,12 +61,27 @@
 
 public class CfSourceCode implements SourceCode {
 
+  private enum GeneratedMethodSynchronizationBlock {
+    METHOD_ENTER,
+    METHOD_EXIT,
+    EXCEPTIONAL_MONITOR_EXIT,
+    EXCEPTIONAL_THROW,
+    NONE;
+
+    static GeneratedMethodSynchronizationBlock fromOffset(int offset) {
+      assert isExceptionalExitForMethodSynchronization(offset);
+      return isExceptionalMonitorExitForMethodSynchronization(offset)
+          ? EXCEPTIONAL_MONITOR_EXIT
+          : EXCEPTIONAL_THROW;
+    }
+  }
+
   private BlockInfo currentBlockInfo;
   private boolean hasExitingInstruction = false;
 
-  private static final int EXCEPTIONAL_SYNC_EXIT_OFFSET = -2;
   private final boolean needsGeneratedMethodSynchronization;
-  private boolean currentlyGeneratingMethodSynchronization = false;
+  private GeneratedMethodSynchronizationBlock currentlyGeneratingMethodSynchronization =
+      GeneratedMethodSynchronizationBlock.NONE;
   private Monitor monitorEnter = null;
 
   private static class TryHandlerList {
@@ -129,7 +146,7 @@
       }
       if (needsGeneratedMethodSynchronization && !seenCatchAll) {
         guards.add(factory.throwableType);
-        offsets.add(EXCEPTIONAL_SYNC_EXIT_OFFSET);
+        offsets.add(EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET);
       }
       return new TryHandlerList(startOffset, endOffset, guards, offsets);
     }
@@ -239,6 +256,18 @@
             && getMethod().isSynchronized();
   }
 
+  void setCurrentlyGeneratingMethodSynchronization(
+      GeneratedMethodSynchronizationBlock currentlyGeneratingMethodSynchronization) {
+    assert this.currentlyGeneratingMethodSynchronization
+        == GeneratedMethodSynchronizationBlock.NONE;
+    this.currentlyGeneratingMethodSynchronization = currentlyGeneratingMethodSynchronization;
+  }
+
+  void unsetCurrentlyGeneratingMethodSynchronization() {
+    assert currentlyGeneratingMethodSynchronization != GeneratedMethodSynchronizationBlock.NONE;
+    currentlyGeneratingMethodSynchronization = GeneratedMethodSynchronizationBlock.NONE;
+  }
+
   private DexEncodedMethod getMethod() {
     return method.getDefinition();
   }
@@ -272,7 +301,6 @@
   @Override
   public int traceInstruction(int instructionIndex, IRBuilder builder) {
     CfInstruction instruction = code.getInstructions().get(instructionIndex);
-    AppView<?> appView = builder.appView;
     assert appView.options().isGeneratingClassFiles()
         == internalOutputMode.isGeneratingClassFiles();
     if (instruction.canThrow()) {
@@ -393,16 +421,30 @@
   }
 
   private boolean isCurrentlyGeneratingMethodSynchronization() {
-    return currentlyGeneratingMethodSynchronization;
+    return currentlyGeneratingMethodSynchronization != GeneratedMethodSynchronizationBlock.NONE;
   }
 
-  private boolean isExceptionalExitForMethodSynchronization(int instructionIndex) {
-    return instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET;
+  private boolean isCurrentlyGeneratingExceptionalMonitorExitForMethodSynchronization() {
+    return currentlyGeneratingMethodSynchronization
+        == GeneratedMethodSynchronizationBlock.EXCEPTIONAL_MONITOR_EXIT;
+  }
+
+  public static boolean isExceptionalMonitorExitForMethodSynchronization(int instructionIndex) {
+    return instructionIndex == EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET;
+  }
+
+  public static boolean isExceptionalThrowForMethodSynchronization(int instructionIndex) {
+    return instructionIndex == EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET;
+  }
+
+  public static boolean isExceptionalExitForMethodSynchronization(int instructionIndex) {
+    return isExceptionalMonitorExitForMethodSynchronization(instructionIndex)
+        || isExceptionalThrowForMethodSynchronization(instructionIndex);
   }
 
   private void buildMethodEnterSynchronization(IRBuilder builder) {
     assert needsGeneratedMethodSynchronization;
-    currentlyGeneratingMethodSynchronization = true;
+    setCurrentlyGeneratingMethodSynchronization(GeneratedMethodSynchronizationBlock.METHOD_ENTER);
     DexType type = method.getHolderType();
     int monitorRegister;
     if (getMethod().isStatic()) {
@@ -414,24 +456,30 @@
     }
     // Build the monitor enter and save it for when generating exits later.
     monitorEnter = builder.addMonitor(MonitorType.ENTER, monitorRegister);
-    currentlyGeneratingMethodSynchronization = false;
+    unsetCurrentlyGeneratingMethodSynchronization();
   }
 
-  private void buildExceptionalExitMethodSynchronization(IRBuilder builder) {
+  private void buildExceptionalExitForMethodSynchronization(
+      IRBuilder builder, int instructionIndex) {
     assert needsGeneratedMethodSynchronization;
-    currentlyGeneratingMethodSynchronization = true;
-    state.setPosition(getCanonicalDebugPositionAtOffset(EXCEPTIONAL_SYNC_EXIT_OFFSET));
-    builder.add(new Monitor(MonitorType.EXIT, monitorEnter.inValues().get(0)));
-    builder.addThrow(getMoveExceptionRegister(0));
-    currentlyGeneratingMethodSynchronization = false;
+    setCurrentlyGeneratingMethodSynchronization(
+        GeneratedMethodSynchronizationBlock.fromOffset(instructionIndex));
+    state.setPosition(getCanonicalDebugPositionAtOffset(instructionIndex));
+    if (isExceptionalMonitorExitForMethodSynchronization(instructionIndex)) {
+      builder.add(new Monitor(MonitorType.EXIT, monitorEnter.object()));
+      builder.addGoto(EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET);
+    } else {
+      builder.addThrow(getMoveExceptionRegister(0));
+    }
+    unsetCurrentlyGeneratingMethodSynchronization();
   }
 
   @Override
   public void buildPostlude(IRBuilder builder) {
     if (needsGeneratedMethodSynchronization) {
-      currentlyGeneratingMethodSynchronization = true;
-      builder.add(new Monitor(MonitorType.EXIT, monitorEnter.inValues().get(0)));
-      currentlyGeneratingMethodSynchronization = false;
+      setCurrentlyGeneratingMethodSynchronization(GeneratedMethodSynchronizationBlock.METHOD_EXIT);
+      builder.add(new Monitor(MonitorType.EXIT, monitorEnter.object()));
+      unsetCurrentlyGeneratingMethodSynchronization();
     }
   }
 
@@ -484,7 +532,7 @@
   public void buildInstruction(
       IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
     if (isExceptionalExitForMethodSynchronization(instructionIndex)) {
-      buildExceptionalExitMethodSynchronization(builder);
+      buildExceptionalExitForMethodSynchronization(builder, instructionIndex);
       return;
     }
     CfInstruction instruction = code.getInstructions().get(instructionIndex);
@@ -507,7 +555,7 @@
 
     if (instruction.canThrow()) {
       Snapshot exceptionTransfer =
-          state.getSnapshot().exceptionTransfer(builder.appView.dexItemFactory().throwableType);
+          state.getSnapshot().exceptionTransfer(appView.dexItemFactory().throwableType);
       for (int target : currentBlockInfo.exceptionalSuccessors) {
         recordStateForTarget(target, exceptionTransfer);
       }
@@ -820,12 +868,19 @@
     if (inPrelude) {
       return null;
     }
+    TryHandlerList tryHandlers;
     if (isCurrentlyGeneratingMethodSynchronization()) {
-      return null;
+      if (!isCurrentlyGeneratingExceptionalMonitorExitForMethodSynchronization()) {
+        return null;
+      }
+      // Ensure that the exceptional monitor-exit instruction is guarded by a catch-all handler that
+      // repeats the monitor-exit.
+      tryHandlers =
+          getTryHandlers(EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET, appView.dexItemFactory());
+    } else {
+      tryHandlers =
+          getTryHandlers(instructionOffset(currentInstructionIndex), appView.dexItemFactory());
     }
-    TryHandlerList tryHandlers =
-        getTryHandlers(
-            instructionOffset(currentInstructionIndex), builder.appView.dexItemFactory());
     if (tryHandlers.isEmpty()) {
       return null;
     }
@@ -857,7 +912,7 @@
 
   @Override
   public Position getCanonicalDebugPositionAtOffset(int offset) {
-    if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
+    if (isExceptionalExitForMethodSynchronization(offset)) {
       return canonicalPositions.getExceptionalExitPosition(
           appView.options().debug,
           () ->
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 8d03811..2c84747 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -14,6 +14,9 @@
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getNull;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getSingle;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getWide;
+import static com.android.tools.r8.ir.conversion.CfSourceCode.isExceptionalExitForMethodSynchronization;
+import static com.android.tools.r8.ir.conversion.CfSourceCode.isExceptionalMonitorExitForMethodSynchronization;
+import static com.android.tools.r8.ir.conversion.CfSourceCode.isExceptionalThrowForMethodSynchronization;
 
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.errors.CompilationError;
@@ -156,6 +159,14 @@
 
   public static final int INITIAL_BLOCK_OFFSET = -1;
 
+  // The (synthetic) offset of the monitor-exit instruction on the exceptional exit, when desugaring
+  // declared synchronized methods.
+  public static final int EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET = -2;
+
+  // The (synthetic) offset of the throw instruction that follows the monitor-exit instruction on
+  // the exceptional exit, when desugaring declared synchronized methods.
+  public static final int EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET = -3;
+
   private static TypeElement fromMemberType(MemberType type) {
     switch (type) {
       case BOOLEAN_OR_BYTE:
@@ -378,6 +389,8 @@
   @SuppressWarnings("JdkObsolete")
   private final Queue<Integer> traceBlocksWorklist = new LinkedList<>();
 
+  private boolean processedExceptionalMonitorExitForMethodSynchronization;
+
   // Bitmap to ensure we don't process an instruction more than once.
   private boolean[] processedInstructions = null;
 
@@ -631,6 +644,9 @@
     traceBlocksWorklist.add(0);
     while (!traceBlocksWorklist.isEmpty()) {
       int startOfBlockOffset = traceBlocksWorklist.remove();
+      if (handleExceptionalExitForMethodSynchronization(startOfBlockOffset)) {
+        continue;
+      }
       int startOfBlockIndex = source.instructionIndex(startOfBlockOffset);
       // Check that the block has not been processed after being added.
       if (isIndexProcessed(startOfBlockIndex)) {
@@ -2470,14 +2486,24 @@
   // Ensure there is a block starting at offset and add it to the work-list if it needs processing.
   private BlockInfo ensureBlock(int offset) {
     // We don't enqueue negative targets (these are special blocks, eg, an argument prelude).
-    if (offset >= 0 && !isOffsetProcessed(offset)) {
+    if (!isOffsetProcessed(offset)) {
       traceBlocksWorklist.add(offset);
     }
     return ensureBlockWithoutEnqueuing(offset);
   }
 
   private boolean isOffsetProcessed(int offset) {
-    return isIndexProcessed(source.instructionIndex(offset));
+    if (offset >= 0) {
+      return isIndexProcessed(source.instructionIndex(offset));
+    }
+    if (isExceptionalMonitorExitForMethodSynchronization(offset)) {
+      return isExceptionalMonitorExitForMethodSynchronizationProcessed();
+    } else {
+      // We never need to process this block since it has no successors and its predecessor is set
+      // up when processing the monitor-exit block.
+      assert isExceptionalThrowForMethodSynchronization(offset);
+      return true;
+    }
   }
 
   private boolean isIndexProcessed(int index) {
@@ -2498,6 +2524,34 @@
     processedSubroutineInstructions.add(index);
   }
 
+  private boolean handleExceptionalExitForMethodSynchronization(int startOfBlockOffset) {
+    if (!isExceptionalExitForMethodSynchronization(startOfBlockOffset)) {
+      return false;
+    }
+    // We never need to process the throw block. We therefore treat it as always processed, meaning
+    // it should never be traced here.
+    assert isExceptionalMonitorExitForMethodSynchronization(startOfBlockOffset);
+    if (markExceptionalMonitorExitForMethodSynchronizationProcessed()) {
+      ensureNormalSuccessorBlock(
+          EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET, EXCEPTIONAL_SYNC_EXIT_THROW_OFFSET);
+      ensureExceptionalSuccessorBlock(
+          EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET, EXCEPTIONAL_SYNC_EXIT_MONITOR_EXIT_OFFSET);
+    }
+    return true;
+  }
+
+  private boolean isExceptionalMonitorExitForMethodSynchronizationProcessed() {
+    return processedExceptionalMonitorExitForMethodSynchronization;
+  }
+
+  private boolean markExceptionalMonitorExitForMethodSynchronizationProcessed() {
+    if (isExceptionalMonitorExitForMethodSynchronizationProcessed()) {
+      return false;
+    }
+    processedExceptionalMonitorExitForMethodSynchronization = true;
+    return true;
+  }
+
   private void ensureSubroutineProcessedInstructions() {
     if (processedSubroutineInstructions == null) {
       processedSubroutineInstructions = new HashSet<>();
@@ -2732,4 +2786,3 @@
     return builder.toString();
   }
 }
-
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
index dea79c9..052c9ad 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
@@ -408,10 +408,11 @@
               },
               fieldPattern ->
                   holder.forEachProgramFieldMatching(
-                      f -> predicates.matchesField(f, fieldPattern), continueWithMember),
+                      f -> predicates.matchesField(f, fieldPattern, appInfo), continueWithMember),
               methodPattern ->
                   holder.forEachProgramMethodMatching(
-                      m -> predicates.matchesMethod(m, methodPattern), continueWithMember));
+                      m -> predicates.matchesMethod(m, methodPattern, appInfo),
+                      continueWithMember));
       if (didContinue.isFalse()) {
         // No match for the member pattern existed, continue with next class.
         continueWithNoClassClearingMembers(
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
index 0982747..dfe3f51 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -84,7 +85,16 @@
   }
 
   private boolean matchesInstanceOfPattern(
-      DexProgramClass clazz, KeepInstanceOfPattern pattern, AppInfoWithClassHierarchy appInfo) {
+      DexType type, KeepInstanceOfPattern pattern, AppInfoWithClassHierarchy appInfo) {
+    DexClass dexClass = appInfo.definitionFor(type);
+    if (dexClass != null) {
+      return matchesInstanceOfPattern(dexClass.asProgramClass(), pattern, appInfo);
+    }
+    return false;
+  }
+
+  private boolean matchesInstanceOfPattern(
+      DexClass clazz, KeepInstanceOfPattern pattern, AppInfoWithClassHierarchy appInfo) {
     if (pattern.isAny()) {
       return true;
     }
@@ -115,23 +125,25 @@
         && matchesGeneralMemberAccess(member.getAccessFlags(), pattern.getAccessPattern());
   }
 
-  public boolean matchesMethod(DexEncodedMethod method, KeepMethodPattern pattern) {
+  public boolean matchesMethod(
+      DexEncodedMethod method, KeepMethodPattern pattern, AppInfoWithClassHierarchy appInfo) {
     if (pattern.isAnyMethod()) {
       return true;
     }
     return matchesString(method.getName(), pattern.getNamePattern().asStringPattern())
-        && matchesReturnType(method.getReturnType(), pattern.getReturnTypePattern())
-        && matchesParameters(method.getParameters(), pattern.getParametersPattern())
+        && matchesReturnType(method.getReturnType(), pattern.getReturnTypePattern(), appInfo)
+        && matchesParameters(method.getParameters(), pattern.getParametersPattern(), appInfo)
         && matchesAnnotatedBy(method.annotations(), pattern.getAnnotatedByPattern())
         && matchesMethodAccess(method.getAccessFlags(), pattern.getAccessPattern());
   }
 
-  public boolean matchesField(DexEncodedField field, KeepFieldPattern pattern) {
+  public boolean matchesField(
+      DexEncodedField field, KeepFieldPattern pattern, AppInfoWithClassHierarchy appInfo) {
     if (pattern.isAnyField()) {
       return true;
     }
     return matchesString(field.getName(), pattern.getNamePattern().asStringPattern())
-        && matchesType(field.getType(), pattern.getTypePattern().asType())
+        && matchesType(field.getType(), pattern.getTypePattern().asType(), appInfo)
         && matchesAnnotatedBy(field.annotations(), pattern.getAnnotatedByPattern())
         && matchesFieldAccess(field.getAccessFlags(), pattern.getAccessPattern());
   }
@@ -211,7 +223,10 @@
     return false;
   }
 
-  public boolean matchesParameters(DexTypeList parameters, KeepMethodParametersPattern pattern) {
+  public boolean matchesParameters(
+      DexTypeList parameters,
+      KeepMethodParametersPattern pattern,
+      AppInfoWithClassHierarchy appInfo) {
     if (pattern.isAny()) {
       return true;
     }
@@ -221,7 +236,7 @@
     }
     int size = parameters.size();
     for (int i = 0; i < size; i++) {
-      if (!matchesType(parameters.get(i), patternList.get(i))) {
+      if (!matchesType(parameters.get(i), patternList.get(i), appInfo)) {
         return false;
       }
     }
@@ -263,29 +278,34 @@
   }
 
   public boolean matchesReturnType(
-      DexType returnType, KeepMethodReturnTypePattern returnTypePattern) {
+      DexType returnType,
+      KeepMethodReturnTypePattern returnTypePattern,
+      AppInfoWithClassHierarchy appInfo) {
     if (returnTypePattern.isAny()) {
       return true;
     }
     if (returnTypePattern.isVoid()) {
       return returnType.isVoidType();
     }
-    return matchesType(returnType, returnTypePattern.asType());
+    return matchesType(returnType, returnTypePattern.asType(), appInfo);
   }
 
-  public boolean matchesType(DexType type, KeepTypePattern pattern) {
+  public boolean matchesType(
+      DexType type, KeepTypePattern pattern, AppInfoWithClassHierarchy appInfo) {
     return pattern.apply(
         () -> true,
         p -> matchesPrimitiveType(type, p),
-        p -> matchesArrayType(type, p),
-        p -> matchesClassType(type, p));
+        p -> matchesArrayType(type, p, appInfo),
+        p -> matchesClassType(type, p),
+        p -> matchesInstanceOfPattern(type, p, appInfo));
   }
 
   public boolean matchesClassType(DexType type, KeepQualifiedClassNamePattern pattern) {
     return type.isClassType() && matchesClassName(type, pattern);
   }
 
-  public boolean matchesArrayType(DexType type, KeepArrayTypePattern pattern) {
+  public boolean matchesArrayType(
+      DexType type, KeepArrayTypePattern pattern, AppInfoWithClassHierarchy appInfo) {
     if (!type.isArrayType()) {
       return false;
     }
@@ -296,7 +316,9 @@
       return false;
     }
     return matchesType(
-        type.toArrayElementAfterDimension(pattern.getDimensions(), factory), pattern.getBaseType());
+        type.toArrayElementAfterDimension(pattern.getDimensions(), factory),
+        pattern.getBaseType(),
+        appInfo);
   }
 
   public boolean matchesPrimitiveType(DexType type, KeepPrimitiveTypePattern pattern) {
diff --git a/src/main/java/com/android/tools/r8/utils/CfUtils.java b/src/main/java/com/android/tools/r8/utils/CfUtils.java
new file mode 100644
index 0000000..52911b4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CfUtils.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+
+import java.io.IOException;
+import java.io.InputStream;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+
+public class CfUtils {
+  private static class ClassNameExtractor extends ClassVisitor {
+    private String className;
+
+    private ClassNameExtractor() {
+      super(ASM_VERSION);
+    }
+
+    @Override
+    public void visit(
+        int version,
+        int access,
+        String name,
+        String signature,
+        String superName,
+        String[] interfaces) {
+      className = name;
+    }
+
+    String getClassInternalType() {
+      return className;
+    }
+  }
+
+  public static String extractClassName(byte[] ccc) {
+    return DescriptorUtils.getJavaTypeFromBinaryName(
+        extractClassInternalType(new ClassReader(ccc)));
+  }
+
+  public static String extractClassDescriptor(byte[] ccc) {
+    return "L" + extractClassInternalType(new ClassReader(ccc)) + ";";
+  }
+
+  public static String extractClassDescriptor(InputStream input) throws IOException {
+    return DescriptorUtils.getDescriptorFromClassBinaryName(
+        extractClassInternalType(new ClassReader(input)));
+  }
+
+  private static String extractClassInternalType(ClassReader reader) {
+    ClassNameExtractor extractor = new ClassNameExtractor();
+    reader.accept(
+        extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+    return extractor.getClassInternalType();
+  }
+}
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 91fbada..b3d395b 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.B;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
 
 import com.android.tools.r8.AndroidResourceConsumer;
@@ -2025,6 +2026,31 @@
     public boolean enableLibraryApiModeling =
         System.getProperty("com.android.tools.r8.disableApiModeling") == null;
 
+    // Flag to specify Android extension libraries (also known as OEM-implemented shared libraries
+    // or sidecars). All APIs within these libraries are handled as having an API level
+    // higher than any existing API level as these APIs might not exist on any device independent
+    // of API level (the nature of an extension API).
+    public String androidApiExtensionLibraries =
+        System.getProperty("com.android.tools.r8.androidApiExtensionLibraries");
+
+    public void forEachAndroidApiExtensionClassDescriptor(Consumer<String> consumer) {
+      if (androidApiExtensionLibraries != null) {
+        StringUtils.split(androidApiExtensionLibraries, ',')
+            .forEach(
+                lib -> {
+                  try {
+                    ZipUtils.iter(
+                        Paths.get(lib),
+                        (entry, input) -> consumer.accept(extractClassDescriptor(input)));
+                  } catch (IOException e) {
+                    throw new CompilationError("Failed to read extension library " + lib, e);
+                  }
+                });
+      }
+    }
+
+    // TODO(b/326252366): Remove support for list of extension packages in favour of only
+    //  supporting passing extension libraries as JAR files.
     // Flag to specify packages for Android extension APIs (also known as OEM-implemented
     // shared libraries or sidecars). The packages are specified as java package names
     // separated by commas. All APIs within these packages are handled as having an API level
@@ -2035,6 +2061,12 @@
     public String androidApiExtensionPackages =
         System.getProperty("com.android.tools.r8.androidApiExtensionPackages");
 
+    public void forEachAndroidApiExtensionPackage(Consumer<String> consumer) {
+      if (androidApiExtensionPackages != null) {
+        StringUtils.split(androidApiExtensionPackages, ',').forEach(consumer);
+      }
+    }
+
     // The flag enableApiCallerIdentification controls if we can inline or merge targets with
     // different api levels. It is also the flag that specifies if we assign api levels to
     // references.
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
index e1fea1f..f6583d2 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -95,6 +95,10 @@
     this.staticizedMethods = staticizedMethods;
   }
 
+  public boolean hasBeenMerged(DexType type) {
+    return mergedClasses.hasBeenMergedIntoSubtype(type);
+  }
+
   public boolean hasInterfaceBeenMergedIntoClass(DexType type) {
     return mergedClasses.hasInterfaceBeenMergedIntoClass(type);
   }
diff --git a/src/test/examplesJava11/nesthostexample/NestOnProgramAndClasspathAndLibraryTest.java b/src/test/examplesJava11/nesthostexample/NestOnProgramAndClasspathAndLibraryTest.java
new file mode 100644
index 0000000..767b7df
--- /dev/null
+++ b/src/test/examplesJava11/nesthostexample/NestOnProgramAndClasspathAndLibraryTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package nesthostexample;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assume;
+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 NestOnProgramAndClasspathAndLibraryTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean alsoOnClasspath;
+
+  @Parameters(name = "{0}, alsoOnClasspath: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  @Test
+  public void testD8MethodBridges() {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    // 1 inner class.
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            compileClassesWithD8ProgramClasses(
+                BasicNestHostWithInnerClassMethods.BasicNestedClass.class,
+                true,
+                BasicNestHostWithInnerClassMethods.class));
+    // Outer class.
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            compileClassesWithD8ProgramClasses(
+                BasicNestHostWithInnerClassMethods.class,
+                false,
+                BasicNestHostWithInnerClassMethods.class));
+    // 2 inner classes.
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            compileClassesWithD8ProgramClasses(
+                NestHostExample.StaticNestMemberInner.class, true, NestHostExample.class));
+  }
+
+  @Test
+  public void testD8ConstructorBridges() {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            compileClassesWithD8ProgramClasses(
+                BasicNestHostWithInnerClassConstructors.BasicNestedClass.class,
+                true,
+                BasicNestHostWithInnerClassConstructors.class));
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            compileClassesWithD8ProgramClasses(
+                BasicNestHostWithInnerClassConstructors.class,
+                false,
+                BasicNestHostWithInnerClassConstructors.class));
+  }
+
+  private void compileClassesWithD8ProgramClasses(
+      Class<?> clazz, boolean withInnerClasses, Class<?> outer) throws Exception {
+    Path nestZip = buildNestZip(outer);
+    testForD8()
+        .setMinApi(parameters)
+        .addProgramClasses(clazz)
+        .applyIf(withInnerClasses, b -> b.addInnerClasses(clazz))
+        .applyIf(alsoOnClasspath, b -> b.addClasspathFiles(nestZip))
+        .addLibraryFiles(nestZip)
+        .compile();
+    Files.delete(nestZip);
+  }
+
+  private Path buildNestZip(Class<?> outer) throws IOException {
+    List<Path> nest = new ArrayList<>();
+    nest.add(ToolHelper.getClassFileForTestClass(outer));
+    nest.addAll(ToolHelper.getClassFilesForInnerClasses(outer));
+    Path zip = temp.getRoot().toPath().resolve("nest.zip");
+    ZipBuilder zp = ZipBuilder.builder(zip);
+    nest.forEach(
+        f -> {
+          try {
+            zp.addFile(f.getFileName().toString(), f);
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          }
+        });
+    return zp.build();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
index 4bdba09..58a8ac6 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -48,6 +48,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 public class AndroidApiHashingDatabaseBuilderGenerator extends TestBase {
 
@@ -199,7 +200,31 @@
     expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NfcB;"));
     expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/Ndef;"));
     expectedMissingMembers.add(factory.createType("Landroid/webkit/CookieSyncManager;"));
-    assertEquals(expectedMissingMembers, missingMemberInformation.keySet());
+    expectedMissingMembers.add(
+        factory.createType("Landroid/adservices/customaudience/CustomAudienceManager;"));
+    expectedMissingMembers.add(
+        factory.createType("Landroid/adservices/customaudience/PartialCustomAudience$Builder;"));
+    expectedMissingMembers.add(
+        factory.createType("Landroid/adservices/customaudience/PartialCustomAudience;"));
+    expectedMissingMembers.add(
+        factory.createType(
+            "Landroid/adservices/customaudience/ScheduleCustomAudienceUpdateRequest$Builder;"));
+    expectedMissingMembers.add(
+        factory.createType(
+            "Landroid/adservices/customaudience/ScheduleCustomAudienceUpdateRequest;"));
+    expectedMissingMembers.add(factory.createType("Landroid/app/appsearch/AppSearchResult;"));
+    assertEquals(
+        expectedMissingMembers.stream()
+                .map(DexType::toDescriptorString)
+                .sorted()
+                .collect(Collectors.joining("\n"))
+            + "\n---\n"
+            + missingMemberInformation.keySet().stream()
+                .map(DexType::toDescriptorString)
+                .sorted()
+                .collect(Collectors.joining("\n")),
+        expectedMissingMembers,
+        missingMemberInformation.keySet());
     return true;
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 1244c0f..0521f40 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.apimodel;
 
+import static com.android.tools.r8.androidapi.AndroidApiLevelDatabaseHelper.notModeledFields;
+import static com.android.tools.r8.androidapi.AndroidApiLevelDatabaseHelper.notModeledMethods;
 import static com.android.tools.r8.androidapi.AndroidApiLevelDatabaseHelper.notModeledTypes;
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 import static org.junit.Assert.assertEquals;
@@ -110,9 +112,9 @@
                   methodReferences.forEach(field -> numberOfMethods.increment())));
         });
     // These numbers will change when updating api-versions.xml
-    assertEquals(5971, parsedApiClasses.size());
+    assertEquals(5972, parsedApiClasses.size());
     assertEquals(30341, numberOfFields.get());
-    assertEquals(46572, numberOfMethods.get());
+    assertEquals(46576, numberOfMethods.get());
   }
 
   @Test
@@ -134,6 +136,8 @@
   private static void ensureAllPublicMethodsAreMapped(
       AppView<AppInfoWithClassHierarchy> appView, AndroidApiLevelCompute apiLevelCompute) {
     Set<String> notModeledTypes = notModeledTypes();
+    Set<String> notModeledFields = notModeledFields();
+    Set<String> notModeledMethods = notModeledMethods();
     for (DexLibraryClass clazz : appView.app().asDirect().libraryClasses()) {
       if (notModeledTypes.contains(clazz.getClassReference().getTypeName())) {
         continue;
@@ -144,7 +148,9 @@
               .isKnownApiLevel());
       clazz.forEachClassField(
           field -> {
-            if (field.getAccessFlags().isPublic() && !field.toSourceString().contains("this$0")) {
+            if (field.getAccessFlags().isPublic()
+                && !field.toSourceString().contains("this$0")
+                && !notModeledFields.contains(field.toSourceString())) {
               assertTrue(
                   apiLevelCompute
                       .computeApiLevelForLibraryReference(field.getReference())
@@ -153,7 +159,8 @@
           });
       clazz.forEachClassMethod(
           method -> {
-            if (method.getAccessFlags().isPublic()) {
+            if (method.getAccessFlags().isPublic()
+                && !notModeledMethods.contains(method.toSourceString())) {
               assertTrue(
                   apiLevelCompute
                       .computeApiLevelForLibraryReference(method.getReference())
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 7617ca1..e87664f 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -53,13 +53,11 @@
   private Set<String> getDeletedTypesMissingRemovedAttribute() {
     Set<String> removedTypeNames = new HashSet<>();
     if (maxApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.U)) {
-      removedTypeNames.add("com.android.internal.util.Predicate");
+      if (maxApiLevel.isLessThan(AndroidApiLevel.V)) {
+        removedTypeNames.add("com.android.internal.util.Predicate");
+      }
       removedTypeNames.add("android.adservices.AdServicesVersion");
     }
-    if (maxApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.V)) {
-      removedTypeNames.add("android.media.MediaDrm$HdcpLevel");
-      removedTypeNames.add("android.media.MediaDrm$SecurityLevel");
-    }
     return removedTypeNames;
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiMultipleExtensionPackagesTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiMultipleExtensionPackagesTest.java
index eebab11..ce3b3d2 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiMultipleExtensionPackagesTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiMultipleExtensionPackagesTest.java
@@ -13,11 +13,15 @@
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.apimodel.another_extension.AnotherExtensionApiLibraryClass;
 import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryClass;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,29 +36,50 @@
           "ExtensionApiLibraryClass::extensionApi",
           "AnotherExtensionApiLibraryClass::extensionApi");
 
-  @Parameter public TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameter(1)
+  public boolean useExtensionJar;
+
+  @Parameters(name = "{0}, useExtensionJar = {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  private static final List<Class<?>> extensionLibraryClasses =
+      ImmutableList.of(ExtensionApiLibraryClass.class, AnotherExtensionApiLibraryClass.class);
+  private static Path extensionLibraryJar;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    extensionLibraryJar =
+        zipWithTestClasses(
+            getStaticTemp().newFolder().toPath().resolve("extension.jar"), extensionLibraryClasses);
   }
 
   private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
     testBuilder
-        .addLibraryClasses(ExtensionApiLibraryClass.class, AnotherExtensionApiLibraryClass.class)
+        .addLibraryFiles(extensionLibraryJar)
         .addDefaultRuntimeLibrary(parameters)
         .addInnerClasses(getClass())
         .setMinApi(parameters)
         .addOptionsModification(
             options -> {
-              String typeName = ExtensionApiLibraryClass.class.getTypeName();
-              String anotherTypeName = AnotherExtensionApiLibraryClass.class.getTypeName();
-              options.apiModelingOptions().androidApiExtensionPackages =
-                  typeName.substring(0, typeName.lastIndexOf('.'))
-                      + ','
-                      + anotherTypeName.substring(0, anotherTypeName.lastIndexOf('.'))
-                      + ','
-                      + typeName.substring(0, typeName.lastIndexOf('.'));
+              if (useExtensionJar) {
+                options.apiModelingOptions().androidApiExtensionLibraries =
+                    extensionLibraryJar.toString();
+              } else {
+                String typeName = ExtensionApiLibraryClass.class.getTypeName();
+                String anotherTypeName = AnotherExtensionApiLibraryClass.class.getTypeName();
+                options.apiModelingOptions().androidApiExtensionPackages =
+                    typeName.substring(0, typeName.lastIndexOf('.'))
+                        + ','
+                        + anotherTypeName.substring(0, anotherTypeName.lastIndexOf('.'))
+                        + ','
+                        + typeName.substring(0, typeName.lastIndexOf('.'));
+              }
             })
         // TODO(b/213552119): Remove when enabled by default.
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
@@ -63,8 +88,7 @@
   }
 
   private void populateBootClasspath(TestCompileResult<?, ?> compilerResult) throws Exception {
-    compilerResult.addBootClasspathClasses(
-        ExtensionApiLibraryClass.class, AnotherExtensionApiLibraryClass.class);
+    compilerResult.addBootClasspathClasses(extensionLibraryClasses);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiSubpackageTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiSubpackageTest.java
index d40af72..9fbdd53 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiSubpackageTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiSubpackageTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
@@ -18,7 +19,9 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
 import java.util.List;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -42,36 +45,75 @@
   @Parameter(2)
   public boolean subpackageIsExtension;
 
-  @Parameters(name = "{0}, packageIsExtension = {1}, subpackageIsExtension = {2}")
+  @Parameter(3)
+  public boolean useExtensionJar;
+
+  @Parameters(
+      name = "{0}, packageIsExtension = {1}, subpackageIsExtension = {2}, useExtensionJar = {3}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
         BooleanUtils.values(),
+        BooleanUtils.values(),
         BooleanUtils.values());
   }
 
+  private static Path extensionLibraryJar;
+  private static Path subPackagExtensionLibraryJar;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    extensionLibraryJar =
+        zipWithTestClasses(
+            getStaticTemp().newFolder().toPath().resolve("extension.jar"),
+            ExtensionApiLibraryClass.class);
+    subPackagExtensionLibraryJar =
+        zipWithTestClasses(
+            getStaticTemp().newFolder().toPath().resolve("sub_package_extension.jar"),
+            SubpackageExtensionApiLibraryClass.class);
+  }
+
   private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
     testBuilder
-        .addLibraryClasses(ExtensionApiLibraryClass.class, SubpackageExtensionApiLibraryClass.class)
+        .addLibraryFiles(extensionLibraryJar, subPackagExtensionLibraryJar)
         .addDefaultRuntimeLibrary(parameters)
         .addInnerClasses(getClass())
         .setMinApi(parameters)
         .addOptionsModification(
             options -> {
-              StringBuilder androidApiExtensionPackages = new StringBuilder();
-              if (packageIsExtension) {
-                String typeName = ExtensionApiLibraryClass.class.getTypeName();
-                androidApiExtensionPackages.append(typeName, 0, typeName.lastIndexOf('.'));
-              }
-              if (subpackageIsExtension) {
-                String typeName = SubpackageExtensionApiLibraryClass.class.getTypeName();
+              if (useExtensionJar) {
+                StringBuilder androidApiExtensionLibraries = new StringBuilder();
                 if (packageIsExtension) {
-                  androidApiExtensionPackages.append(",");
+                  androidApiExtensionLibraries.append(extensionLibraryJar);
                 }
-                androidApiExtensionPackages.append(typeName, 0, typeName.lastIndexOf('.'));
+                if (subpackageIsExtension) {
+                  if (packageIsExtension) {
+                    androidApiExtensionLibraries.append(",");
+                  }
+                  androidApiExtensionLibraries.append(subPackagExtensionLibraryJar);
+                }
+                if (packageIsExtension || subpackageIsExtension) {
+                  options.apiModelingOptions().androidApiExtensionLibraries =
+                      androidApiExtensionLibraries.toString();
+                } else {
+                  assertTrue(androidApiExtensionLibraries.toString().isEmpty());
+                }
+              } else {
+                StringBuilder androidApiExtensionPackages = new StringBuilder();
+                if (packageIsExtension) {
+                  String typeName = ExtensionApiLibraryClass.class.getTypeName();
+                  androidApiExtensionPackages.append(typeName, 0, typeName.lastIndexOf('.'));
+                }
+                if (subpackageIsExtension) {
+                  String typeName = SubpackageExtensionApiLibraryClass.class.getTypeName();
+                  if (packageIsExtension) {
+                    androidApiExtensionPackages.append(",");
+                  }
+                  androidApiExtensionPackages.append(typeName, 0, typeName.lastIndexOf('.'));
+                }
+                options.apiModelingOptions().androidApiExtensionPackages =
+                    androidApiExtensionPackages.toString();
               }
-              options.apiModelingOptions().androidApiExtensionPackages =
-                  androidApiExtensionPackages.toString();
             })
         // TODO(b/213552119): Remove when enabled by default.
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiTest.java
index 6057fab..8feeec5 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodExtensionApiTest.java
@@ -14,13 +14,17 @@
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryClass;
 import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryInterface;
 import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryInterfaceImpl;
 import com.android.tools.r8.apimodel.extension.ExtensionApiLibraryInterfaceProvider;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -35,27 +39,48 @@
           "ExtensionApiLibraryClass::extensionApi",
           "ExtensionApiLibraryInterfaceImpl::extensionApi");
 
-  @Parameter public TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameter(1)
+  public boolean useExtensionJar;
+
+  @Parameters(name = "{0}, useExtensionJar = {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  private static final List<Class<?>> extensionLibraryClasses =
+      ImmutableList.of(
+          ExtensionApiLibraryClass.class,
+          ExtensionApiLibraryInterface.class,
+          ExtensionApiLibraryInterfaceProvider.class);
+  private static Path extensionLibraryJar;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    extensionLibraryJar =
+        zipWithTestClasses(
+            getStaticTemp().newFolder().toPath().resolve("extension.jar"), extensionLibraryClasses);
   }
 
   private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
     testBuilder
-        .addLibraryClasses(
-            ExtensionApiLibraryClass.class,
-            ExtensionApiLibraryInterface.class,
-            ExtensionApiLibraryInterfaceProvider.class)
+        .addLibraryFiles(extensionLibraryJar)
         .addDefaultRuntimeLibrary(parameters)
         .addInnerClasses(getClass())
         .setMinApi(parameters)
         .addOptionsModification(
             options -> {
-              String typeName = ExtensionApiLibraryClass.class.getTypeName();
-              options.apiModelingOptions().androidApiExtensionPackages =
-                  typeName.substring(0, typeName.lastIndexOf('.'));
+              if (useExtensionJar) {
+                options.apiModelingOptions().androidApiExtensionLibraries =
+                    extensionLibraryJar.toString();
+              } else {
+                String typeName = ExtensionApiLibraryClass.class.getTypeName();
+                options.apiModelingOptions().androidApiExtensionPackages =
+                    typeName.substring(0, typeName.lastIndexOf('.'));
+              }
             })
         // TODO(b/213552119): Remove when enabled by default.
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
@@ -64,11 +89,9 @@
   }
 
   private void populateBootClasspath(TestCompileResult<?, ?> compilerResult) throws Exception {
-    compilerResult.addBootClasspathClasses(
-        ExtensionApiLibraryClass.class,
-        ExtensionApiLibraryInterface.class,
-        ExtensionApiLibraryInterfaceImpl.class,
-        ExtensionApiLibraryInterfaceProvider.class);
+    compilerResult
+        .addBootClasspathClasses(extensionLibraryClasses)
+        .addBootClasspathClasses(ExtensionApiLibraryInterfaceImpl.class);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldReferenceCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldReferenceCollisionTest.java
new file mode 100644
index 0000000..2bb3e18
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldReferenceCollisionTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.horizontal;
+
+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.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+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 NonReboundFieldReferenceCollisionTest 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)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(C.class, B.class).assertNoOtherClassesMerged())
+        // The minifier may assign different names to A.f and B.f, so disable obfuscation so that we
+        // can check that the two fields have the same name through out the compilation.
+        .addDontObfuscate()
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              FieldSubject shadowedFieldSubject = aClassSubject.uniqueFieldWithOriginalName("f");
+              assertThat(shadowedFieldSubject, isPresent());
+
+              ClassSubject bClassSubject = inspector.clazz(B.class);
+              assertThat(bClassSubject, isPresent());
+
+              FieldSubject shadowingFieldSubject = bClassSubject.uniqueFieldWithOriginalName("f");
+              assertThat(shadowingFieldSubject, isPresent());
+              assertEquals(
+                  shadowingFieldSubject.getFinalName(), shadowedFieldSubject.getFinalName());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1", "2", "3", "4");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      B b = System.currentTimeMillis() > 0 ? new B(one(), two()) : new B(three(), four());
+      System.out.println(b.f());
+      System.out.println(b.f);
+      C c = System.currentTimeMillis() > 0 ? new C(three(), four()) : new C(one(), two());
+      System.out.println(c.f);
+      System.out.println(c.g);
+    }
+
+    static int one() {
+      return 1;
+    }
+
+    static int two() {
+      return 2;
+    }
+
+    static int three() {
+      return 3;
+    }
+
+    static int four() {
+      return 4;
+    }
+  }
+
+  @NoVerticalClassMerging
+  abstract static class A {
+
+    int f;
+
+    @NeverInline
+    A(int f) {
+      this.f = f;
+    }
+
+    int f() {
+      return f;
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+
+    int f;
+
+    @NeverInline
+    B(int f, int g) {
+      super(f);
+      this.f = g;
+    }
+  }
+
+  @NeverClassInline
+  static class C extends A {
+
+    int g;
+
+    @NeverInline
+    C(int f, int g) {
+      super(f);
+      this.g = g;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
index f22226d..93c2734 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
@@ -48,11 +48,13 @@
   }
 
   private void inspectVerticallyMergedClasses(VerticallyMergedClassesInspector inspector) {
-    inspector.assertMergedIntoSubtype(
-        GenericInterface.class,
-        GenericAbstractClass.class,
-        Outer.SuperClass.class,
-        SuperClass.class);
+    inspector
+        .assertMergedIntoSubtype(
+            GenericInterface.class,
+            GenericAbstractClass.class,
+            Outer.SuperClass.class,
+            SuperClass.class)
+        .assertNoOtherClassesMerged();
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ShadowedNonReboundFieldVerticalClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ShadowedNonReboundFieldVerticalClassMergingTest.java
new file mode 100644
index 0000000..27e03c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ShadowedNonReboundFieldVerticalClassMergingTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.vertical;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+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 ShadowedNonReboundFieldVerticalClassMergingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1", "2", "2");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(B.class).assertNoOtherClassesMerged())
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1", "2", "2");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      C c = System.currentTimeMillis() > 0 ? new C(1, 2) : new C(3, 4);
+      System.out.println(c.f);
+      System.out.println(c.a());
+      System.out.println(c.b());
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class A {
+
+    int f;
+
+    A(int f) {
+      this.f = f;
+    }
+
+    int a() {
+      return f;
+    }
+  }
+
+  static class B extends A {
+
+    B(int f) {
+      super(f);
+    }
+
+    int b() {
+      return f;
+    }
+  }
+
+  static class C extends B {
+
+    int f;
+
+    C(int f, int g) {
+      super(g);
+      this.f = f;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
index 460bdd7..fadd1f6 100644
--- a/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import static com.android.tools.r8.utils.CfUtils.extractClassName;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ByteDataView;
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 7fcd792..4c2c44a 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.CfUtils.extractClassName;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static java.util.stream.Collectors.toList;
 import static org.hamcrest.CoreMatchers.containsString;
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClasspathAndLibraryPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClasspathAndLibraryPathTest.java
deleted file mode 100644
index c9cf2e3..0000000
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClasspathAndLibraryPathTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.nestaccesscontrol;
-
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.JAR;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-import static java.util.stream.Collectors.toList;
-import static org.hamcrest.core.StringContains.containsString;
-import static org.hamcrest.core.StringEndsWith.endsWith;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import java.nio.file.Path;
-import java.util.List;
-import org.hamcrest.Matcher;
-import org.junit.Assume;
-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 NestOnProgramAndClasspathAndLibraryPathTest extends TestBase {
-
-  public NestOnProgramAndClasspathAndLibraryPathTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
-  }
-
-  @Test
-  public void testD8MethodBridges() {
-    Assume.assumeTrue(parameters.isDexRuntime());
-    // 1 inner class.
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass")));
-    // Outer class.
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                endsWith("BasicNestHostWithInnerClassMethods")));
-    // 2 inner classes.
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                containsString("NestHostExample$StaticNestMemberInner")));
-  }
-
-  @Test
-  public void testD8ConstructorBridges() {
-    Assume.assumeTrue(parameters.isDexRuntime());
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                containsString("BasicNestHostWithInnerClassConstructors$BasicNestedClass")));
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                endsWith("BasicNestHostWithInnerClassConstructors")));
-  }
-
-  private D8TestCompileResult compileClassesWithD8ProgramClassesMatching(Matcher<String> matcher)
-      throws Exception {
-    List<Path> matchingClasses =
-        CLASS_NAMES.stream()
-            .filter(matcher::matches)
-            .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
-            .collect(toList());
-    return testForD8()
-        .setMinApi(parameters)
-        .addProgramFiles(matchingClasses)
-        .addClasspathFiles(JAR)
-        .addLibraryFiles(JAR)
-        .compile();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndLibraryPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndLibraryPathTest.java
deleted file mode 100644
index 74c344e..0000000
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndLibraryPathTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.nestaccesscontrol;
-
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.JAR;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-import static java.util.stream.Collectors.toList;
-import static org.hamcrest.core.StringContains.containsString;
-import static org.hamcrest.core.StringEndsWith.endsWith;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import java.nio.file.Path;
-import java.util.List;
-import org.hamcrest.Matcher;
-import org.junit.Assume;
-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 NestOnProgramAndLibraryPathTest extends TestBase {
-
-  public NestOnProgramAndLibraryPathTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
-  }
-
-  @Test
-  public void testD8MethodBridges() {
-    Assume.assumeTrue(parameters.isDexRuntime());
-    // 1 inner class.
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass")));
-    // Outer class.
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                endsWith("BasicNestHostWithInnerClassMethods")));
-    // 2 inner classes.
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                containsString("NestHostExample$StaticNestMemberInner")));
-  }
-
-  @Test
-  public void testD8ConstructorBridges() {
-    Assume.assumeTrue(parameters.isDexRuntime());
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                containsString("BasicNestHostWithInnerClassConstructors$BasicNestedClass")));
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            compileClassesWithD8ProgramClassesMatching(
-                endsWith("BasicNestHostWithInnerClassConstructors")));
-  }
-
-  private D8TestCompileResult compileClassesWithD8ProgramClassesMatching(Matcher<String> matcher)
-      throws Exception {
-    List<Path> matchingClasses =
-        CLASS_NAMES.stream()
-            .filter(matcher::matches)
-            .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
-            .collect(toList());
-    return testForD8()
-        .setMinApi(parameters)
-        .addProgramFiles(matchingClasses)
-        .addLibraryFiles(JAR)
-        .compile();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java
index 455e037..d55815d 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java
@@ -9,10 +9,12 @@
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.IOException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -63,13 +65,26 @@
         .addProgramClasses(I.class, Main.class)
         .addProgramClassFileData(getClassWithTransformedInvoked())
         .addKeepMainRule(Main.class)
+        // Illegal invoke-super to interface method.
+        .applyIf(
+            parameters.isDexRuntime()
+                && parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K),
+            R8TestBuilder::allowDiagnosticWarningMessages)
         .setMinApi(parameters)
         .run(parameters.getRuntime(), Main.class)
         // TODO(b/313065227): Should succeed.
         .applyIf(
             parameters.isCfRuntime(),
             runResult -> runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
-            runResult -> runResult.assertFailureWithErrorThatThrows(NullPointerException.class));
+            parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik(),
+            runResult -> runResult.assertFailureWithErrorThatThrows(VerifyError.class),
+            parameters.canUseDefaultAndStaticInterfaceMethods(),
+            runResult -> runResult.assertFailureWithErrorThatThrows(NullPointerException.class),
+            parameters.isDexRuntime()
+                && parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V5_1_1, Version.V6_0_1),
+            runResult ->
+                runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class),
+            runResult -> runResult.assertFailureWithErrorThatThrows(AbstractMethodError.class));
   }
 
   private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
index 5b9b506..df1101d 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
@@ -3,12 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -120,7 +120,7 @@
           String entryString = entry.toString();
           if (entryString.endsWith(".class") && !entryString.startsWith("j$")) {
             byte[] bytes = ByteStreams.toByteArray(inputStream);
-            consumer.accept(ByteDataView.of(bytes), TestBase.extractClassDescriptor(bytes), null);
+            consumer.accept(ByteDataView.of(bytes), extractClassDescriptor(bytes), null);
           }
         });
     consumer.finished(null);
diff --git a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
index dd2700f..3a58eeb 100644
--- a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
+++ b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
@@ -3,12 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jacoco;
 
+import static com.android.tools.r8.utils.CfUtils.extractClassName;
 import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.ZipUtils;
@@ -42,7 +42,7 @@
     // Write the class to a .class file with package sub-directories.
     Path original = dir.resolve("original");
     for (byte[] clazz : classes) {
-      String typeName = TestBase.extractClassName(clazz);
+      String typeName = extractClassName(clazz);
       int lastDotIndex = typeName.lastIndexOf('.');
       String pkg = typeName.substring(0, lastDotIndex);
       String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION;
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
index e3622d4..27b74ee 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.keepanno;
 
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
+
 import com.android.tools.r8.ExternalR8TestBuilder;
 import com.android.tools.r8.ProguardTestBuilder;
 import com.android.tools.r8.R8FullTestBuilder;
@@ -189,8 +191,8 @@
       // This enables native interpretation of all keep annotations.
       builder.addOptionsModification(
           o -> {
-            o.testing.enableExtractedKeepAnnotations = true;
-            o.testing.enableEmbeddedKeepAnnotations = true;
+            o.testing.enableExtractedKeepAnnotations = isNormalizeEdges();
+            o.testing.enableEmbeddedKeepAnnotations = !isNormalizeEdges();
           });
       // This disables all reading of annotations in the command reader.
       builder.getBuilder().setEnableExperimentalKeepAnnotations(false);
@@ -257,8 +259,7 @@
         List<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(classFileData);
         if (!declarations.isEmpty()) {
           String binaryName =
-              DescriptorUtils.getBinaryNameFromDescriptor(
-                  TestBase.extractClassDescriptor(classFileData));
+              DescriptorUtils.getBinaryNameFromDescriptor(extractClassDescriptor(classFileData));
           String synthesizingTarget = binaryName + "$$ExtractedKeepEdges";
           ClassWriter classWriter = new ClassWriter(InternalOptions.ASM_VERSION);
           classWriter.visit(
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index b6be4dc..e92c4d8 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -581,7 +581,8 @@
           .addMember(
               new GroupMember("classNamePattern")
                   .setDocTitle("Classes matching the class-name pattern.")
-                  .defaultValue(CLASS_NAME_PATTERN, DEFAULT_INVALID_CLASS_NAME_PATTERN));
+                  .defaultValue(CLASS_NAME_PATTERN, DEFAULT_INVALID_CLASS_NAME_PATTERN))
+          .addMember(instanceOfPattern());
       // TODO(b/248408342): Add more injections on type pattern variants.
       // /** Exact type name as a string to match any array with that type as member. */
       // String arrayOf() default "";
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
index d905d4c..8e0df4f 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -41,7 +41,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
 import kotlin.text.Charsets;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -287,16 +286,6 @@
     runAndCheckOutput(targetClasses, sourceClasses, format, expected, null);
   }
 
-  private Path zipWithTestClasses(Path zipFile, List<Class<?>> targetClasses) throws IOException {
-    return ZipBuilder.builder(zipFile)
-        .addFilesRelative(
-            ToolHelper.getClassPathForTests(),
-            targetClasses.stream()
-                .map(ToolHelper::getClassFileForTestClass)
-                .collect(Collectors.toList()))
-        .build();
-  }
-
   public void runAndCheckOutput(
       List<Class<?>> targetClasses,
       List<Class<?>> sourceClasses,
diff --git a/src/test/java/com/android/tools/r8/utils/DaggerUtils.java b/src/test/java/com/android/tools/r8/utils/DaggerUtils.java
index de4e55f..e10cd49 100644
--- a/src/test/java/com/android/tools/r8/utils/DaggerUtils.java
+++ b/src/test/java/com/android/tools/r8/utils/DaggerUtils.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static com.android.tools.r8.utils.CfUtils.extractClassName;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
@@ -73,7 +74,7 @@
             String entryString = entry.toString();
             if (FileUtils.isClassFile(entryString)) {
               byte[] bytes = ByteStreams.toByteArray(inputStream);
-              classNames.add(TestBase.extractClassName(bytes));
+              classNames.add(extractClassName(bytes));
             }
           });
     }
diff --git a/src/test/java/com/android/tools/r8/workaround/ExpectedToBeWithinCatchAllAfterInliningWithSoftVerificationErrorTest.java b/src/test/java/com/android/tools/r8/workaround/ExpectedToBeWithinCatchAllAfterInliningWithSoftVerificationErrorTest.java
new file mode 100644
index 0000000..316af73
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/workaround/ExpectedToBeWithinCatchAllAfterInliningWithSoftVerificationErrorTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.workaround;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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;
+
+/** Regression test for b/348785664. */
+@RunWith(Parameterized.class)
+public class ExpectedToBeWithinCatchAllAfterInliningWithSoftVerificationErrorTest 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)
+        .applyIf(!canUseJavaUtilObjects(parameters), builder -> builder.addDontWarn(Objects.class))
+        // Disable desugaring to prevent backporting of Objects.isNull.
+        .disableDesugaring()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      try {
+        // After inlining of greet(), if the catch handler responsible for exiting the monitor is
+        // not itself guarded by a catch-all, then verification fails due to the presence of the
+        // non-catch-all handler.
+        greet();
+      } catch (IllegalArgumentException e) {
+        e.printStackTrace();
+      }
+
+      // Verification error only happens when the method has a soft verification error.
+      // We therefore call Objects.isNull, which is only defined from API level 24.
+      if (args.length > 0) {
+        System.out.println(Objects.isNull(args));
+      }
+    }
+
+    static synchronized void greet() {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/RelocatorTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/RelocatorTestBuilder.java
index 2fea242..149ce0e 100644
--- a/src/test/testbase/java/com/android/tools/r8/RelocatorTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/RelocatorTestBuilder.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.android.tools.r8.TestBase.extractClassDescriptor;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
 
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java
index 2a812ee..350bb0f 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.TestBuilder.getTestingAnnotations;
 import static com.android.tools.r8.ToolHelper.R8_TEST_BUCKET;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 import static com.google.common.collect.Lists.cartesianProduct;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -87,6 +88,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -1395,45 +1397,6 @@
     }
   }
 
-  public static String extractClassName(byte[] ccc) {
-    return DescriptorUtils.descriptorToJavaType(extractClassDescriptor(ccc));
-  }
-
-  public static String extractClassDescriptor(byte[] ccc) {
-    return "L" + extractClassInternalType(ccc) + ";";
-  }
-
-  private static String extractClassInternalType(byte[] ccc) {
-    class ClassNameExtractor extends ClassVisitor {
-      private String className;
-
-      private ClassNameExtractor() {
-        super(ASM_VERSION);
-      }
-
-      @Override
-      public void visit(
-          int version,
-          int access,
-          String name,
-          String signature,
-          String superName,
-          String[] interfaces) {
-        className = name;
-      }
-
-      String getClassInternalType() {
-        return className;
-      }
-    }
-
-    ClassReader reader = new ClassReader(ccc);
-    ClassNameExtractor extractor = new ClassNameExtractor();
-    reader.accept(
-        extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
-    return extractor.getClassInternalType();
-  }
-
   protected static void writeClassesToJar(Path output, Collection<Class<?>> classes)
       throws IOException {
     ClassFileConsumer consumer = new ArchiveConsumer(output);
@@ -2157,6 +2120,19 @@
     }
   }
 
+  protected static Path zipWithTestClasses(Path zipFile, Class<?>... classes) throws IOException {
+    return zipWithTestClasses(zipFile, Arrays.asList(classes));
+  }
+
+  protected static Path zipWithTestClasses(Path zipFile, List<Class<?>> classes)
+      throws IOException {
+    return ZipBuilder.builder(zipFile)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(),
+            classes.stream().map(ToolHelper::getClassFileForTestClass).collect(Collectors.toList()))
+        .build();
+  }
+
   private static class GenericSignatureReader extends ClassVisitor {
 
     public final Set<String> signatures = new HashSet<>();
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
index 201d674..a7c2cd8 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import static com.android.tools.r8.TestBase.descriptor;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
 
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.TestBase.Backend;
@@ -241,7 +242,7 @@
     }
     ArchiveConsumer consumer = new ArchiveConsumer(out);
     for (byte[] bytes : classes) {
-      consumer.accept(ByteDataView.of(bytes), TestBase.extractClassDescriptor(bytes), null);
+      consumer.accept(ByteDataView.of(bytes), extractClassDescriptor(bytes), null);
     }
     consumer.finished(null);
     return outputConsumer.apply(out);
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java
index 545a36e..8f32eae 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java
@@ -6,7 +6,7 @@
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.TestBase.Backend.DEX;
 import static com.android.tools.r8.TestBase.testForD8;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -218,7 +218,8 @@
         }
       }
     }
-    assertThat("Did you forget a keep rule for the main method?", mainClassSubject, Matchers.isPresent());
+    assertThat(
+        "Did you forget a keep rule for the main method?", mainClassSubject, Matchers.isPresent());
     if (runtime.isDex()) {
       return runArt(runtime, mainClassSubject.getFinalName(), args);
     }
@@ -295,7 +296,7 @@
       AndroidApp.Builder appBuilder = AndroidApp.builder();
       for (byte[] clazz : classes) {
         appBuilder.addClassProgramData(
-            clazz, Origin.unknown(), ImmutableSet.of(TestBase.extractClassDescriptor(clazz)));
+            clazz, Origin.unknown(), ImmutableSet.of(extractClassDescriptor(clazz)));
       }
       Path path = state.getNewTempFolder().resolve("runtime-classes.jar");
       appBuilder.build().writeToZipForTesting(path, OutputMode.ClassFile);
diff --git a/third_party/android_jar/lib-v35.tar.gz.sha1 b/third_party/android_jar/lib-v35.tar.gz.sha1
index 3543c83..0db22a6 100644
--- a/third_party/android_jar/lib-v35.tar.gz.sha1
+++ b/third_party/android_jar/lib-v35.tar.gz.sha1
@@ -1 +1 @@
-9ac3858ee6859c45500fa7c5254592516c5e73b7
\ No newline at end of file
+661a1420aa7b08e8d8f2e670a808d064ef3edd65
\ No newline at end of file
diff --git a/third_party/api_database/api_database.tar.gz.sha1 b/third_party/api_database/api_database.tar.gz.sha1
index 0d2c06c..966e156c 100644
--- a/third_party/api_database/api_database.tar.gz.sha1
+++ b/third_party/api_database/api_database.tar.gz.sha1
@@ -1 +1 @@
-b043b19332f3f7cd2578874b6d051462b55ac56a
\ No newline at end of file
+0aab829fe216f39035a282024966bbd26bea3a26
\ No newline at end of file