Merge commit '4455f6c687e63ebb44174423d4ef2b499d506c4d' into dev-release
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index ed216b0..5600b2e 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 3,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs",
-  "version": "1.1.1",
+  "version": "1.1.2",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "support_all_callbacks_from_library": true,
@@ -244,7 +244,7 @@
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }",
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }",
     "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); public static final !synthetic <fields>; }",
-    "-keeppackagenames j$",
+    "-keeppackagenames j$.**",
     "-keepclassmembers class j$.util.IntSummaryStatistics { long count; long sum; int min; int max; }",
     "-keepclassmembers class j$.util.LongSummaryStatistics { long count; long sum; long min; long max; }",
     "-keepclassmembers class j$.util.DoubleSummaryStatistics { long count; double sum; double min; double max; }",
diff --git a/src/library_desugar/desugar_jdk_libs_alternative_3.json b/src/library_desugar/desugar_jdk_libs_alternative_3.json
index 012d6ce..cb7f478 100644
--- a/src/library_desugar/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/desugar_jdk_libs_alternative_3.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 3,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs_alternative_3",
-  "version": "1.1.1",
+  "version": "1.2.0",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "support_all_callbacks_from_library": false,
@@ -188,6 +188,7 @@
         "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
         "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
         "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
+        "java.util.Objects": "j$.util.Objects",
         "java.util.Optional": "j$.util.Optional",
         "java.util.PrimitiveIterator": "j$.util.PrimitiveIterator",
         "java.util.Spliterator": "j$.util.Spliterator",
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index fa5a935..4fdf194 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -166,7 +166,7 @@
       AndroidApp inputApp, InternalOptions options, ExecutorService executor, Timing timing)
       throws IOException {
     PrefixRewritingMapper rewritePrefix =
-        options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+        options.desugaredLibraryConfiguration.getPrefixRewritingMapper();
     ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
     LazyLoadedDexApplication app = applicationReader.read(executor);
     AppInfo appInfo = AppInfo.createInitialAppInfo(app, applicationReader.readMainDexClasses(app));
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index a06be47..40f48d2 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -159,7 +159,7 @@
         new ApplicationReader(inputApp, options, timing).read(executor);
 
     PrefixRewritingMapper rewritePrefix =
-        options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+        options.desugaredLibraryConfiguration.getPrefixRewritingMapper();
 
     DexApplication app = new L8TreePruner(options).prune(lazyApp, rewritePrefix);
     return AppView.createForL8(AppInfo.createInitialAppInfo(app), rewritePrefix);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 07b0e0c..6c7f76f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -222,7 +221,11 @@
           if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
             type = Type.DIRECT;
           } else if (code.getOriginalHolder() == method.holder) {
-            type = invokeTypeForInvokeSpecialToNonInitMethodOnHolder(builder.appView, code);
+            MethodAndInvokeType methodAndInvokeType =
+                transformInvokeSpecialToNonInitMethodOnHolder(
+                    builder.appView, code, builder.getProgramMethod());
+            type = methodAndInvokeType.getInvokeType();
+            canonicalMethod = methodAndInvokeType.getMethod();
           } else {
             type = Type.SUPER;
           }
@@ -357,8 +360,26 @@
     return true;
   }
 
-  private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
-      AppView<?> appView, CfSourceCode code) {
+  private static class MethodAndInvokeType {
+    private final DexMethod method;
+    private final Invoke.Type invokeType;
+
+    private MethodAndInvokeType(DexMethod method, Type invokeType) {
+      this.method = method;
+      this.invokeType = invokeType;
+    }
+
+    public DexMethod getMethod() {
+      return method;
+    }
+
+    public Type getInvokeType() {
+      return invokeType;
+    }
+  }
+
+  private MethodAndInvokeType transformInvokeSpecialToNonInitMethodOnHolder(
+      AppView<?> appView, CfSourceCode code, ProgramMethod context) {
     boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
     MethodLookupResult lookupResult = appView.graphLens().lookupMethod(method, method, Type.DIRECT);
     if (lookupResult.getType() == Type.VIRTUAL) {
@@ -366,30 +387,38 @@
       // publicized to be final. For example, if a private method A.m() is publicized, and A is
       // subsequently merged with a class B, with declares a public non-final method B.m(), then the
       // horizontal class merger will merge A.m() and B.m() into a new non-final public method.
-      return Type.VIRTUAL;
+      return new MethodAndInvokeType(method, Type.VIRTUAL);
     }
     DexMethod rewrittenMethod = lookupResult.getReference();
     DexEncodedMethod encodedMethod = lookupMethodOnHolder(appView, rewrittenMethod);
     if (encodedMethod == null) {
       // The method is not defined on the class, we can use super to target. When desugaring
       // default interface methods, it is expected they are targeted with invoke-direct.
-      return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
+      return new MethodAndInvokeType(
+          method, this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER);
     }
     if (encodedMethod.isPrivateMethod() || !encodedMethod.isVirtualMethod()) {
-      return Type.DIRECT;
+      return new MethodAndInvokeType(method, Type.DIRECT);
     }
     if (encodedMethod.accessFlags.isFinal()) {
       // This method is final which indicates no subtype will overwrite it, we can use
       // invoke-virtual.
-      return Type.VIRTUAL;
+      return new MethodAndInvokeType(method, Type.VIRTUAL);
     }
     if (this.itf && encodedMethod.isDefaultMethod()) {
-      return desugaringEnabled ? Type.DIRECT : Type.SUPER;
+      return new MethodAndInvokeType(method, desugaringEnabled ? Type.DIRECT : Type.SUPER);
     }
-    // We cannot emulate the semantics of invoke-special in this case and should throw a compilation
-    // error.
-    throw new CompilationError(
-        "Failed to compile unsupported use of invokespecial", code.getOrigin());
+    assert encodedMethod.isNonPrivateVirtualMethod();
+    assert context.getHolderType() == method.holder;
+    // This is an invoke-special to a virtual method on invoke-special method holder.
+    // The invoke should be rewritten with a bridge.
+    DexMethod directMethod =
+        appView.getInvokeSpecialBridgeSynthesizer().registerBridgeForMethod(encodedMethod);
+    // In R8 the target should have been inserted in the enqueuer,
+    // while in D8, the target is inserted at the end of the compilation.
+    assert appView.enableWholeProgramOptimizations()
+        == (context.getHolder().lookupDirectMethod(directMethod) != null);
+    return new MethodAndInvokeType(directMethod, Type.DIRECT);
   }
 
   private DexEncodedMethod lookupMethodOnHolder(AppView<?> appView, DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 566fa31..d51e21c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.desugar.InvokeSpecialBridgeSynthesizer;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
@@ -67,8 +68,9 @@
       new InstanceFieldInitializationInfoFactory();
   private final MethodProcessingId.Factory methodProcessingIdFactory;
 
-  // Desugared library prefix rewriter.
+  // Desugaring.
   public final PrefixRewritingMapper rewritePrefix;
+  private final InvokeSpecialBridgeSynthesizer invokeSpecialBridgeSynthesizer;
 
   // Modeling.
   private final LibraryMethodSideEffectModelCollection libraryMethodSideEffectModelCollection;
@@ -108,6 +110,7 @@
     this.methodProcessingIdFactory =
         new MethodProcessingId.Factory(options().testing.methodProcessingIdConsumer);
     this.rewritePrefix = mapper;
+    this.invokeSpecialBridgeSynthesizer = new InvokeSpecialBridgeSynthesizer(this);
 
     if (enableWholeProgramOptimizations() && options().callSiteOptimizationOptions().isEnabled()) {
       this.callSiteOptimizationInfoPropagator =
@@ -134,7 +137,7 @@
 
   private static <T extends AppInfo> PrefixRewritingMapper defaultPrefixRewritingMapper(T appInfo) {
     InternalOptions options = appInfo.options();
-    return options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+    return options.desugaredLibraryConfiguration.getPrefixRewritingMapper();
   }
 
   public static <T extends AppInfo> AppView<T> createForD8(T appInfo) {
@@ -294,6 +297,10 @@
     return callSiteOptimizationInfoPropagator;
   }
 
+  public InvokeSpecialBridgeSynthesizer getInvokeSpecialBridgeSynthesizer() {
+    return invokeSpecialBridgeSynthesizer;
+  }
+
   public LibraryMemberOptimizer libraryMethodOptimizer() {
     return libraryMemberOptimizer;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index bcbc160..fb1ad730 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1307,15 +1307,28 @@
     return new ProgramMethod(holder, builder.build());
   }
 
+  public DexEncodedMethod toPrivateSyntheticMethod(DexMethod method) {
+    assert !accessFlags.isStatic();
+    assert !accessFlags.isPrivate();
+    assert getHolderType() == method.holder;
+    checkIfObsolete();
+    Builder builder = syntheticBuilder(this);
+    builder.setMethod(method);
+    builder.accessFlags.setSynthetic();
+    builder.accessFlags.unsetProtected();
+    builder.accessFlags.unsetPublic();
+    builder.accessFlags.setPrivate();
+    return builder.build();
+  }
+
   public DexEncodedMethod toForwardingMethod(DexClass holder, DexDefinitionSupplier definitions) {
+    DexMethod newMethod = method.withHolder(holder.type, definitions.dexItemFactory());
     checkIfObsolete();
     // Clear the final flag, as this method is now overwritten. Do this before creating the builder
     // for the forwarding method, as the forwarding method will copy the access flags from this,
     // and if different forwarding methods are created in different subclasses the first could be
     // final.
     accessFlags.demoteFromFinal();
-    DexMethod newMethod = method.withHolder(holder.type, definitions.dexItemFactory());
-    Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER;
     Builder builder = syntheticBuilder(this);
     builder.setMethod(newMethod);
     if (accessFlags.isAbstract()) {
@@ -1328,10 +1341,10 @@
       ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
           ForwardMethodSourceCode.builder(newMethod);
       forwardSourceCodeBuilder
-          .setReceiver(accessFlags.isStatic() ? null : holder.type)
+          .setReceiver(accessFlags.isStatic() ? null : newMethod.getHolderType())
           .setTargetReceiver(accessFlags.isStatic() ? null : method.holder)
           .setTarget(method)
-          .setInvokeType(type)
+          .setInvokeType(accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER)
           .setIsInterface(target.isInterface());
       builder.setCode(
           new SynthesizedCode(
@@ -1516,7 +1529,7 @@
   public static class Builder {
 
     private DexMethod method;
-    private final MethodAccessFlags accessFlags;
+    private MethodAccessFlags accessFlags;
     private final MethodTypeSignature genericSignature;
     private final DexAnnotationSet annotations;
     private OptionalBool isLibraryMethodOverride = OptionalBool.UNKNOWN;
@@ -1555,6 +1568,10 @@
       }
     }
 
+    public void setAccessFlags(MethodAccessFlags accessFlags) {
+      this.accessFlags = accessFlags.copy();
+    }
+
     public void setMethod(DexMethod method) {
       this.method = method;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 1ace989..71d89ef 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -588,6 +588,11 @@
     methodCollection.addDirectMethod(directMethod);
   }
 
+  public void replaceVirtualMethod(
+      DexMethod virtualMethod, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    methodCollection.replaceVirtualMethod(virtualMethod, replacement);
+  }
+
   public void replaceInterfaces(List<ClassTypeSignature> newInterfaces) {
     if (newInterfaces.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 641ef08..c5f516b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -185,7 +185,7 @@
   /** Returns true if this field is read by the program. */
   @Override
   public boolean isRead() {
-    return !readsWithContexts.isEmpty() || isReadFromAnnotation();
+    return !readsWithContexts.isEmpty() || isReadFromAnnotation() || isReadFromMethodHandle();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
index 7ba6207..1af8be2 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -75,6 +75,10 @@
     superInvokes.forEach(consumer);
   }
 
+  public void forEachSuperInvokeContext(DexMethod method, Consumer<ProgramMethod> consumer) {
+    superInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer);
+  }
+
   public void forEachVirtualInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
     virtualInvokes.forEach(consumer);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 3387ff2..e88d379 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -233,7 +233,7 @@
   }
 
   public boolean verifyAllocatedTypesAreLive(
-      Set<DexType> liveTypes, DexDefinitionSupplier definitions) {
+      Set<DexType> liveTypes, Set<DexType> missingTypes, DexDefinitionSupplier definitions) {
     for (DexProgramClass clazz : classesWithAllocationSiteTracking.keySet()) {
       assert liveTypes.contains(clazz.getType());
     }
@@ -244,7 +244,9 @@
       assert liveTypes.contains(iface.getType());
     }
     for (DexType iface : instantiatedLambdas.keySet()) {
-      assert definitions.definitionFor(iface).isNotProgramClass() || liveTypes.contains(iface);
+      assert missingTypes.contains(iface)
+          || definitions.definitionFor(iface).isNotProgramClass()
+          || liveTypes.contains(iface);
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 3adad2e..b42cb24 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -122,17 +122,17 @@
     }
 
     void fixupField(DexField oldFieldSignature, DexField newFieldSignature) {
+      DexField representative = fieldMap.removeRepresentativeFor(oldFieldSignature);
       Set<DexField> originalFieldSignatures = fieldMap.removeValue(oldFieldSignature);
       if (originalFieldSignatures.isEmpty()) {
         fieldMap.put(oldFieldSignature, newFieldSignature);
       } else if (originalFieldSignatures.size() == 1) {
         fieldMap.put(originalFieldSignatures.iterator().next(), newFieldSignature);
       } else {
+        assert representative != null;
         for (DexField originalFieldSignature : originalFieldSignatures) {
           fieldMap.put(originalFieldSignature, newFieldSignature);
         }
-        DexField representative = fieldMap.removeRepresentativeFor(oldFieldSignature);
-        assert representative != null;
         fieldMap.setRepresentative(newFieldSignature, representative);
       }
     }
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 c1d51db..27a8c72 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
@@ -490,6 +490,10 @@
     return method.getDefinition();
   }
 
+  public ProgramMethod getProgramMethod() {
+    return method;
+  }
+
   public RewrittenPrototypeDescription getPrototypeChanges() {
     return prototypeChanges;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index a6203e9..e2b0e47 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -463,6 +463,12 @@
     }
   }
 
+  private void synthesizeInvokeSpecialBridges(ExecutorService executorService)
+      throws ExecutionException {
+    assert !appView.enableWholeProgramOptimizations();
+    appView.getInvokeSpecialBridgeSynthesizer().insertBridgesForD8(this, executorService);
+  }
+
   private void synthesizeEnumUnboxingUtilityMethods(ExecutorService executorService)
       throws ExecutionException {
     if (enumUnboxer != null) {
@@ -521,7 +527,7 @@
     synthesizeTwrCloseResourceUtilityClass(builder, executor);
     processSynthesizedJava8UtilityClasses(executor);
     synthesizeRetargetClass(builder, executor);
-
+    synthesizeInvokeSpecialBridges(executor);
     processCovariantReturnTypeAnnotations(builder);
     generateDesugaredLibraryAPIWrappers(builder, executor);
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 11e6e33..d6adc3c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -82,7 +82,7 @@
       AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
     List<DexMethod> methods = new ArrayList<>();
     PrefixRewritingMapper rewritePrefix =
-        options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+        options.desugaredLibraryConfiguration.getPrefixRewritingMapper();
     AppInfo appInfo = null;
     if (androidApp != null) {
       DexApplication app =
@@ -193,10 +193,13 @@
 
   private static final class RewritableMethods {
 
+    private final AppView<?> appView;
+
     // Map backported method to a provider for creating the actual target method (with code).
     private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
 
     RewritableMethods(InternalOptions options, AppView<?> appView) {
+      this.appView = appView;
 
       if (!options.shouldBackportMethods()) {
         return;
@@ -205,7 +208,7 @@
       DexItemFactory factory = options.itemFactory;
 
       if (options.minApiLevel < AndroidApiLevel.K.getLevel()) {
-        initializeAndroidKMethodProviders(factory);
+        initializeAndroidKMethodProviders(factory, appView);
       }
       if (options.minApiLevel < AndroidApiLevel.N.getLevel()) {
         initializeAndroidNMethodProviders(factory);
@@ -246,7 +249,7 @@
       rewritable.keySet().forEach(consumer);
     }
 
-    private void initializeAndroidKMethodProviders(DexItemFactory factory) {
+    private void initializeAndroidKMethodProviders(DexItemFactory factory, AppView<?> appView) {
       // Byte
       DexType type = factory.boxedByteType;
       // int Byte.compare(byte a, byte b)
@@ -1305,6 +1308,21 @@
     }
 
     private void addProvider(MethodProvider generator) {
+      if (appView.options().desugaredLibraryConfiguration.isSupported(generator.method, appView)) {
+        // TODO(b/174453232): Remove this after the configuration file format has bee updated
+        // with the "rewrite_method" section.
+        if (generator.method.getHolderType() == appView.dexItemFactory().objectsType) {
+          // Still backport the new API level 30 methods.
+          String methodName = generator.method.getName().toString();
+          if (!methodName.equals("requireNonNullElse")
+              && !methodName.equals("requireNonNullElseGet")
+              && !methodName.equals("checkIndex")
+              && !methodName.equals("checkFromToIndex")
+              && !methodName.equals("checkFromIndexSize")) {
+            return;
+          }
+        }
+      }
       MethodProvider replaced = rewritable.put(generator.method, generator);
       assert replaced == null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 5627d6e..f5ff0b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -50,7 +50,8 @@
           ImmutableMap.of(),
           ImmutableSet.of(),
           ImmutableList.of(),
-          ImmutableList.of());
+          ImmutableList.of(),
+          PrefixRewritingMapper.empty());
   private final AndroidApiLevel requiredCompilationAPILevel;
   private final boolean libraryCompilation;
   private final String synthesizedLibraryClassesPackagePrefix;
@@ -72,13 +73,14 @@
   private final List<Pair<DexType, DexString>> dontRewriteInvocation;
   private final List<String> extraKeepRules;
   private final Set<DexType> wrapperConversions;
+  private final PrefixRewritingMapper prefixRewritingMapper;
 
   public static Builder builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
     return new Builder(dexItemFactory, reporter, origin);
   }
 
   public static DesugaredLibraryConfiguration withOnlyRewritePrefixForTesting(
-      Map<String, String> prefix) {
+      Map<String, String> prefix, InternalOptions options) {
     return new DesugaredLibraryConfiguration(
         AndroidApiLevel.B,
         true,
@@ -93,7 +95,8 @@
         ImmutableMap.of(),
         ImmutableSet.of(),
         ImmutableList.of(),
-        ImmutableList.of());
+        ImmutableList.of(),
+        new DesugarPrefixRewritingMapper(prefix, options.itemFactory, true));
   }
 
   public static DesugaredLibraryConfiguration empty() {
@@ -114,7 +117,8 @@
       Map<DexType, DexType> customConversions,
       Set<DexType> wrapperConversions,
       List<Pair<DexType, DexString>> dontRewriteInvocation,
-      List<String> extraKeepRules) {
+      List<String> extraKeepRules,
+      PrefixRewritingMapper prefixRewritingMapper) {
     this.requiredCompilationAPILevel = requiredCompilationAPILevel;
     this.libraryCompilation = libraryCompilation;
     this.synthesizedLibraryClassesPackagePrefix = packagePrefix;
@@ -129,12 +133,11 @@
     this.wrapperConversions = wrapperConversions;
     this.dontRewriteInvocation = dontRewriteInvocation;
     this.extraKeepRules = extraKeepRules;
+    this.prefixRewritingMapper = prefixRewritingMapper;
   }
 
-  public PrefixRewritingMapper createPrefixRewritingMapper(InternalOptions options) {
-    return rewritePrefix.isEmpty()
-        ? PrefixRewritingMapper.empty()
-        : new DesugarPrefixRewritingMapper(rewritePrefix, options);
+  public PrefixRewritingMapper getPrefixRewritingMapper() {
+    return prefixRewritingMapper;
   }
 
   public AndroidApiLevel getRequiredCompilationApiLevel() {
@@ -160,6 +163,11 @@
   public Map<DexType, DexType> getEmulateLibraryInterface() {
     return emulateLibraryInterface;
   }
+
+  public boolean isSupported(DexMethod method, AppView<?> appView) {
+    return prefixRewritingMapper.hasRewrittenType(method.getHolderType(), appView);
+  }
+
   // If the method is retargeted, answers the retargeted method, else null.
   public DexMethod retargetMethod(DexEncodedMethod method, AppView<?> appView) {
     Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.getName());
@@ -385,7 +393,10 @@
           ImmutableMap.copyOf(customConversions),
           ImmutableSet.copyOf(wrapperConversions),
           ImmutableList.copyOf(dontRewriteInvocation),
-          ImmutableList.copyOf(extraKeepRules));
+          ImmutableList.copyOf(extraKeepRules),
+          rewritePrefix.isEmpty()
+              ? PrefixRewritingMapper.empty()
+              : new DesugarPrefixRewritingMapper(rewritePrefix, factory, libraryCompilation));
     }
 
     private void validate() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InvokeSpecialBridgeSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/InvokeSpecialBridgeSynthesizer.java
new file mode 100644
index 0000000..51003cb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InvokeSpecialBridgeSynthesizer.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.google.common.collect.Sets;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * It is possible in class files to have an invoke-special to a virtual method in the same class
+ * than the method holding the invoke-special. Such invoke-special are executed correctly on the
+ * JVM, but cannot be expressed in terms of invoke-direct or invoke-super in dex. This class
+ * introduces bridges to support the case described: the virtual method code is moved to a private
+ * synthetic method, and a bridging virtual method with the initial method name and flags is
+ * inserted.
+ */
+public class InvokeSpecialBridgeSynthesizer {
+
+  private static final String INVOKE_SPECIAL_BRIDGE_PREFIX = "$invoke$special$";
+
+  private final AppView<?> appView;
+
+  private final Map<DexMethod, DexMethod> bridges = new ConcurrentHashMap<>();
+  private final Set<DexMethod> seenBridges = Sets.newIdentityHashSet();
+
+  public InvokeSpecialBridgeSynthesizer(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  public DexMethod registerBridgeForMethod(DexEncodedMethod method) {
+    assert method.isVirtualMethod();
+    assert !method.getAccessFlags().isFinal();
+    return bridges.computeIfAbsent(
+        method.getReference(),
+        vMethod ->
+            vMethod.withName(
+                appView
+                    .dexItemFactory()
+                    .createString(INVOKE_SPECIAL_BRIDGE_PREFIX + vMethod.name.toString()),
+                appView.dexItemFactory()));
+  }
+
+  // In R8, insertBridgesForR8 is called multiple times until fixed point.
+  // The bridges are inserted prior to IR conversion.
+  public SortedProgramMethodSet insertBridgesForR8() {
+    SortedProgramMethodSet insertedDirectMethods = SortedProgramMethodSet.create();
+    bridges.forEach(
+        (vMethod, dMethod) -> {
+          if (seenBridges.add(vMethod)) {
+            insertedDirectMethods.add(insertBridge(getVirtualMethod(vMethod), dMethod));
+          }
+        });
+    return insertedDirectMethods;
+  }
+
+  // In D8, insertBridgesForD8 is called once.
+  // The bridges are inserted after IR conversion hence the bridges need to be processed.
+  public void insertBridgesForD8(IRConverter converter, ExecutorService executorService)
+      throws ExecutionException {
+    SortedProgramMethodSet insertedBridges = SortedProgramMethodSet.create();
+    bridges.forEach(
+        (virtualMethod, directMethod) -> {
+          ProgramMethod programVirtualMethod = getVirtualMethod(virtualMethod);
+          insertBridge(programVirtualMethod, directMethod);
+          insertedBridges.add(programVirtualMethod);
+        });
+    converter.processMethodsConcurrently(insertedBridges, executorService);
+  }
+
+  private ProgramMethod getVirtualMethod(DexMethod virtualMethod) {
+    DexProgramClass holder = appView.definitionFor(virtualMethod.holder).asProgramClass();
+    assert holder.lookupVirtualMethod(virtualMethod) != null;
+    DexEncodedMethod encodedVirtualMethod = holder.lookupVirtualMethod(virtualMethod);
+    return new ProgramMethod(holder, encodedVirtualMethod);
+  }
+
+  private ProgramMethod insertBridge(ProgramMethod virtualMethod, DexMethod directMethod) {
+    assert virtualMethod.getHolderType() == directMethod.holder;
+    DexProgramClass holder = virtualMethod.getHolder();
+    assert holder.lookupDirectMethod(directMethod) == null;
+    DexEncodedMethod initialVirtualMethod = virtualMethod.getDefinition();
+    DexEncodedMethod newDirectMethod = initialVirtualMethod.toPrivateSyntheticMethod(directMethod);
+    CfCode forwardingCode =
+        ForwardMethodBuilder.builder(appView.dexItemFactory())
+            .setDirectTarget(directMethod, holder.isInterface())
+            .setNonStaticSource(virtualMethod.getReference())
+            .build();
+    initialVirtualMethod.setCode(forwardingCode, appView);
+    initialVirtualMethod.markNotProcessed();
+    holder.addDirectMethod(newDirectMethod);
+    return new ProgramMethod(holder, newDirectMethod);
+  }
+
+  public EnqueuerInvokeAnalysis getEnqueuerInvokeAnalysis() {
+    return new InvokeSpecialBridgeAnalysis();
+  }
+
+  private class InvokeSpecialBridgeAnalysis implements EnqueuerInvokeAnalysis {
+
+    @Override
+    public void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {}
+
+    @Override
+    public void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
+      DexEncodedMethod lookup = context.getHolder().lookupMethod(invokedMethod);
+      if (lookup != null
+          && lookup.isNonPrivateVirtualMethod()
+          && context.getHolderType() == invokedMethod.holder
+          && !context.getHolder().isInterface()
+          && !lookup.accessFlags.isFinal()) {
+        registerBridgeForMethod(lookup);
+      }
+    }
+
+    @Override
+    public void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {}
+
+    @Override
+    public void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {}
+
+    @Override
+    public void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {}
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
index 9624c2a..8ad22c7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.util.Map;
@@ -58,9 +57,10 @@
     private final DexItemFactory factory;
     private final boolean l8Compilation;
 
-    public DesugarPrefixRewritingMapper(Map<String, String> prefixes, InternalOptions options) {
-      this.factory = options.itemFactory;
-      this.l8Compilation = options.isDesugaredLibraryCompilation();
+    public DesugarPrefixRewritingMapper(
+        Map<String, String> prefixes, DexItemFactory itemFactory, boolean libraryCompilation) {
+      this.factory = itemFactory;
+      this.l8Compilation = libraryCompilation;
       ImmutableMap.Builder<DexString, DexString> builder = ImmutableMap.builder();
       for (String key : prefixes.keySet()) {
         builder.put(toDescriptorPrefix(key), toDescriptorPrefix(prefixes.get(key)));
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 43a0511..1fdb08f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -98,11 +98,26 @@
         CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler);
   }
 
+  public static ClassNameMapper mapperFromString(
+      String contents, DiagnosticsHandler diagnosticsHandler, boolean allowEmptyMappedRanges)
+      throws IOException {
+    return mapperFromBufferedReader(
+        CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler, allowEmptyMappedRanges);
+  }
+
   private static ClassNameMapper mapperFromBufferedReader(
       BufferedReader reader, DiagnosticsHandler diagnosticsHandler) throws IOException {
+    return mapperFromBufferedReader(reader, diagnosticsHandler, false);
+  }
+
+  private static ClassNameMapper mapperFromBufferedReader(
+      BufferedReader reader, DiagnosticsHandler diagnosticsHandler, boolean allowEmptyMappedRanges)
+      throws IOException {
     try (ProguardMapReader proguardReader =
         new ProguardMapReader(
-            reader, diagnosticsHandler != null ? diagnosticsHandler : new Reporter())) {
+            reader,
+            diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
+            allowEmptyMappedRanges)) {
       ClassNameMapper.Builder builder = ClassNameMapper.builder();
       proguardReader.parse(builder);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 28cdcf5..12ea2a6 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -458,7 +458,6 @@
     private MappedRange(
         Range minifiedRange, MethodSignature signature, Object originalRange, String renamedName) {
 
-      assert minifiedRange != null || originalRange == null;
       assert originalRange == null
           || originalRange instanceof Integer
           || originalRange instanceof Range;
@@ -510,7 +509,7 @@
         builder.append(minifiedRange).append(':');
       }
       builder.append(signature);
-      if (originalRange != null && !minifiedRange.equals(originalRange)) {
+      if (originalRange != null && !originalRange.equals(minifiedRange)) {
         builder.append(":").append(originalRange);
       }
       builder.append(" -> ").append(renamedName);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 49b46cd..7ddb042 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -63,15 +63,20 @@
   private final BufferedReader reader;
   private final JsonParser jsonParser = new JsonParser();
   private final DiagnosticsHandler diagnosticsHandler;
+  private final boolean allowEmptyMappedRanges;
 
   @Override
   public void close() throws IOException {
     reader.close();
   }
 
-  ProguardMapReader(BufferedReader reader, DiagnosticsHandler diagnosticsHandler) {
+  ProguardMapReader(
+      BufferedReader reader,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean allowEmptyMappedRanges) {
     this.reader = reader;
     this.diagnosticsHandler = diagnosticsHandler;
+    this.allowEmptyMappedRanges = allowEmptyMappedRanges;
     assert reader != null;
     assert diagnosticsHandler != null;
   }
@@ -293,7 +298,7 @@
           throw new ParseException("No number follows the colon after the method signature.");
         }
       }
-      if (mappedRange == null && originalRange != null) {
+      if (!allowEmptyMappedRanges && mappedRange == null && originalRange != null) {
         throw new ParseException("No mapping for original range " + originalRange + ".");
       }
 
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index 5c50c06..927cf6e 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -74,7 +74,7 @@
   private static SeedMapper seedMapperFromInputStream(Reporter reporter, InputStream in)
       throws IOException {
     BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
-    try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter)) {
+    try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter, false)) {
       SeedMapper.Builder builder = SeedMapper.builder(reporter);
       proguardReader.parse(builder);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index cea7fc2..ea4ee0e 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -31,6 +32,7 @@
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -85,6 +87,11 @@
         .excludeInterfaces()
         .visit(appView.appInfo().classes(), clazz -> processClass(clazz, subtypingInfo));
     if (!result.isEmpty()) {
+      // Mapping from non-hoisted bridge methods to the set of contexts in which they are accessed.
+      MethodAccessInfoCollection.IdentityBuilder bridgeMethodAccessInfoCollectionBuilder =
+          MethodAccessInfoCollection.identityBuilder();
+      result.recordNonReboundMethodAccesses(bridgeMethodAccessInfoCollectionBuilder);
+
       BridgeHoistingLens lens = result.buildLens();
       appView.rewriteWithLens(lens);
 
@@ -96,7 +103,7 @@
       // bridge methods are left as-is in the code, but they are rewritten to the hoisted bridges
       // during the rewriting of AppInfoWithLiveness. Therefore, this conservatively records that
       // there may be an invoke-virtual instruction that targets each of the removed bridges.
-      methodAccessInfoCollectionModifier.addAll(result.getBridgeMethodAccessInfoCollection());
+      methodAccessInfoCollectionModifier.addAll(bridgeMethodAccessInfoCollectionBuilder.build());
 
       // Additionally, we record the invokes from the newly synthesized bridge methods.
       result.forEachHoistedBridge(
@@ -203,7 +210,9 @@
     List<DexProgramClass> eligibleSubclasses = mostFrequentBridge.getValue();
 
     // Choose one of the bridge definitions as the one that we will be moving to the superclass.
-    ProgramMethod representative = findRepresentative(eligibleSubclasses, method);
+    List<ProgramMethod> eligibleBridgeMethods =
+        getBridgesEligibleForHoisting(eligibleSubclasses, method);
+    ProgramMethod representative = eligibleBridgeMethods.iterator().next();
 
     // Guard against accessibility issues.
     if (mayBecomeInaccessibleAfterHoisting(clazz, representative)) {
@@ -237,7 +246,10 @@
       newMethod.getAccessFlags().demoteFromFinal();
     }
     clazz.addVirtualMethod(newMethod);
-    result.move(representative.getReference(), newMethodReference);
+    result.move(
+        Iterables.transform(eligibleBridgeMethods, DexClassAndMember::getReference),
+        newMethodReference,
+        representative.getReference());
 
     // Remove all of the bridges in the eligible subclasses.
     for (DexProgramClass subclass : eligibleSubclasses) {
@@ -260,14 +272,17 @@
     return result;
   }
 
-  private ProgramMethod findRepresentative(Iterable<DexProgramClass> subclasses, DexMethod method) {
+  private List<ProgramMethod> getBridgesEligibleForHoisting(
+      Iterable<DexProgramClass> subclasses, DexMethod reference) {
+    List<ProgramMethod> result = new ArrayList<>();
     for (DexProgramClass subclass : subclasses) {
-      DexEncodedMethod definition = subclass.lookupVirtualMethod(method);
-      if (definition != null) {
-        return new ProgramMethod(subclass, definition);
+      ProgramMethod method = subclass.lookupProgramMethod(reference);
+      if (method != null) {
+        result.add(method);
       }
     }
-    throw new Unreachable();
+    assert !result.isEmpty();
+    return result;
   }
 
   private boolean mayBecomeInaccessibleAfterHoisting(
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
index 9353111..4439aea 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
@@ -11,8 +11,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
+import com.google.common.collect.Sets;
 import java.util.Set;
 import java.util.function.BiConsumer;
 
@@ -21,12 +22,8 @@
   private final AppView<AppInfoWithLiveness> appView;
 
   // Mapping from non-hoisted bridge methods to hoisted bridge methods.
-  private final MutableBidirectionalManyToOneMap<DexMethod, DexMethod> bridgeToHoistedBridgeMap =
-      new BidirectionalManyToOneHashMap<>();
-
-  // Mapping from non-hoisted bridge methods to the set of contexts in which they are accessed.
-  private final MethodAccessInfoCollection.IdentityBuilder bridgeMethodAccessInfoCollectionBuilder =
-      MethodAccessInfoCollection.identityBuilder();
+  private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
+      bridgeToHoistedBridgeMap = new BidirectionalManyToOneRepresentativeHashMap<>();
 
   BridgeHoistingResult(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
@@ -43,30 +40,46 @@
         });
   }
 
-  public MethodAccessInfoCollection getBridgeMethodAccessInfoCollection() {
-    return bridgeMethodAccessInfoCollectionBuilder.build();
-  }
-
   public boolean isEmpty() {
     return bridgeToHoistedBridgeMap.isEmpty();
   }
 
-  public void move(DexMethod from, DexMethod to) {
-    Set<DexMethod> keys = bridgeToHoistedBridgeMap.getKeys(from);
-    if (keys.isEmpty()) {
-      bridgeToHoistedBridgeMap.put(from, to);
-    } else {
-      for (DexMethod original : keys) {
-        bridgeToHoistedBridgeMap.put(original, to);
+  public void move(Iterable<DexMethod> from, DexMethod to, DexMethod representative) {
+    DexMethod originalRepresentative =
+        bridgeToHoistedBridgeMap.getRepresentativeKeyOrDefault(representative, representative);
+    Set<DexMethod> originalFrom = Sets.newLinkedHashSet();
+    for (DexMethod method : from) {
+      Set<DexMethod> keys = bridgeToHoistedBridgeMap.removeValue(method);
+      if (keys.isEmpty()) {
+        originalFrom.add(method);
+      } else {
+        originalFrom.addAll(keys);
       }
     }
+    assert originalFrom.contains(originalRepresentative);
+    bridgeToHoistedBridgeMap.put(originalFrom, to);
+    bridgeToHoistedBridgeMap.setRepresentative(to, originalRepresentative);
+  }
 
+  public void recordNonReboundMethodAccesses(
+      MethodAccessInfoCollection.IdentityBuilder bridgeMethodAccessInfoCollectionBuilder) {
     MethodAccessInfoCollection methodAccessInfoCollection =
         appView.appInfo().getMethodAccessInfoCollection();
-    methodAccessInfoCollection.forEachVirtualInvokeContext(
-        from,
-        context ->
-            bridgeMethodAccessInfoCollectionBuilder.registerInvokeVirtualInContext(from, context));
+    bridgeToHoistedBridgeMap
+        .keySet()
+        .forEach(
+            from -> {
+              methodAccessInfoCollection.forEachSuperInvokeContext(
+                  from,
+                  context ->
+                      bridgeMethodAccessInfoCollectionBuilder.registerInvokeSuperInContext(
+                          from, context));
+              methodAccessInfoCollection.forEachVirtualInvokeContext(
+                  from,
+                  context ->
+                      bridgeMethodAccessInfoCollectionBuilder.registerInvokeVirtualInContext(
+                          from, context));
+            });
   }
 
   public BridgeHoistingLens buildLens() {
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index ef0bff7..d202954 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
 import static com.android.tools.r8.utils.ExceptionUtils.STATUS_ERROR;
 import static com.android.tools.r8.utils.ExceptionUtils.failWithFakeEntry;
 
@@ -35,9 +36,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Scanner;
+import java.util.Set;
 import java.util.function.BiConsumer;
 
 /**
@@ -207,6 +210,7 @@
       boolean isVerbose) {
     List<String> retracedStrings = new ArrayList<>();
     Box<StackTraceElementStringProxy> lastReportedFrame = new Box<>();
+    Set<String> seenSetForLastReportedFrame = new HashSet<>();
     run(
         stackTraceVisitor,
         StackTraceElementProxyRetracer.createDefault(retracer),
@@ -214,10 +218,22 @@
           frames.forEach(
               frame -> {
                 StackTraceElementStringProxy originalItem = frame.getOriginalItem();
-                retracedStrings.add(
-                    originalItem.toRetracedItem(
-                        frame, lastReportedFrame.get() == stackTraceElement, isVerbose));
-                lastReportedFrame.set(stackTraceElement);
+                boolean newFrame =
+                    lastReportedFrame.getAndSet(stackTraceElement) != stackTraceElement;
+                if (newFrame) {
+                  seenSetForLastReportedFrame.clear();
+                }
+                String retracedString = originalItem.toRetracedItem(frame, isVerbose);
+                if (seenSetForLastReportedFrame.add(retracedString)) {
+                  if (frame.isAmbiguous() && !newFrame) {
+                    int firstCharIndex = firstNonWhiteSpaceCharacterFromIndex(retracedString, 0);
+                    retracedString =
+                        retracedString.substring(0, firstCharIndex)
+                            + "<OR> "
+                            + retracedString.substring(firstCharIndex);
+                  }
+                  retracedStrings.add(retracedString);
+                }
               });
         });
     return retracedStrings;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
index 90fedbd..9272f58 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import static com.google.common.base.Predicates.not;
+import static com.android.tools.r8.retrace.internal.RetraceUtils.firstCharFromIndex;
+import static com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.retrace.RetraceInvalidStackTraceLineDiagnostics;
@@ -14,7 +15,6 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.List;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 
 public final class PlainStackTraceVisitor
     implements StackTraceVisitor<StackTraceElementStringProxy> {
@@ -34,22 +34,6 @@
     }
   }
 
-  static int firstNonWhiteSpaceCharacterFromIndex(String line, int index) {
-    return firstFromIndex(line, index, not(Character::isWhitespace));
-  }
-
-  static int firstCharFromIndex(String line, int index, char ch) {
-    return firstFromIndex(line, index, c -> c == ch);
-  }
-
-  static int firstFromIndex(String line, int index, Predicate<Character> predicate) {
-    for (int i = index; i < line.length(); i++) {
-      if (predicate.test(line.charAt(i))) {
-        return i;
-      }
-    }
-    return line.length();
-  }
 
   /**
    * Captures a stack trace line of the following formats:
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index 35f1f63..341ac8d 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -124,9 +124,12 @@
 
   private RetracedMethodImpl getRetracedMethod(
       MethodReference methodReference, MappedRange mappedRange, int obfuscatedPosition) {
-    if (obfuscatedPosition == -1
-        || mappedRange.minifiedRange == null
-        || !mappedRange.minifiedRange.contains(obfuscatedPosition)) {
+    if (mappedRange.minifiedRange == null) {
+      int originalLineNumber = mappedRange.getFirstLineNumberOfOriginalRange();
+      return RetracedMethodImpl.create(
+          methodReference, originalLineNumber > 0 ? originalLineNumber : obfuscatedPosition);
+    }
+    if (obfuscatedPosition == -1 || !mappedRange.minifiedRange.contains(obfuscatedPosition)) {
       return RetracedMethodImpl.create(methodReference);
     }
     return RetracedMethodImpl.create(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
index d475594..fb79984 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
@@ -166,7 +166,8 @@
     }
   }
 
-  private static final String anyLetterWithMarkers = "\\p{L}\\p{M}*+";
+  private static final String anyNonDigitLetterCharWithMarkers = "\\p{L}\\p{M}*+";
+  private static final String anyDigit = "\\p{N}";
 
   // TODO(b/145731185): Extend support for identifiers with strings inside back ticks.
   private static final String javaIdentifierSegment =
@@ -275,7 +276,10 @@
 
     @Override
     String subExpression() {
-      return "(?:([" + anyLetterWithMarkers + "_: \\.]*[" + anyLetterWithMarkers + "_\\.])?)";
+      String anyNonDigitSourceFileChar = anyNonDigitLetterCharWithMarkers + "_ \\.";
+      String anyChar = anyNonDigitSourceFileChar + anyDigit;
+      String colonWithNonDigitSuffix = ":[" + anyNonDigitSourceFileChar + ":" + "]";
+      return "((?:(?:(?:" + colonWithNonDigitSuffix + "))|(?:[" + anyChar + "]))+)?";
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index dc96687..0ca25c7 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.retrace.internal;
 
+import static com.google.common.base.Predicates.not;
+
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
@@ -24,6 +26,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Predicate;
 
 public class RetraceUtils {
 
@@ -147,4 +150,21 @@
         formalTypes,
         returnType);
   }
+
+  public static int firstNonWhiteSpaceCharacterFromIndex(String line, int index) {
+    return firstFromIndex(line, index, not(Character::isWhitespace));
+  }
+
+  public static int firstCharFromIndex(String line, int index, char ch) {
+    return firstFromIndex(line, index, c -> c == ch);
+  }
+
+  public static int firstFromIndex(String line, int index, Predicate<Character> predicate) {
+    for (int i = index; i < line.length(); i++) {
+      if (predicate.test(line.charAt(i))) {
+        return i;
+      }
+    }
+    return line.length();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index eed4f28..c2a6749 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -32,7 +32,7 @@
     }
     try {
       ClassNameMapper classNameMapper =
-          ClassNameMapper.mapperFromString(proguardMapProducer.get(), diagnosticsHandler);
+          ClassNameMapper.mapperFromString(proguardMapProducer.get(), diagnosticsHandler, true);
       return new RetracerImpl(classNameMapper);
     } catch (Throwable throwable) {
       throw new InvalidMappingFileException(throwable);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index 5a76e44..f0ba88d 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import static com.android.tools.r8.retrace.internal.PlainStackTraceVisitor.firstNonWhiteSpaceCharacterFromIndex;
 import static com.android.tools.r8.retrace.internal.RetraceUtils.methodDescriptionFromRetraceMethod;
 import static com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StringIndex.noIndex;
 
@@ -136,16 +135,9 @@
   }
 
   public String toRetracedItem(
-      RetraceStackTraceProxy<StackTraceElementStringProxy> retracedProxy,
-      boolean printAmbiguous,
-      boolean verbose) {
+      RetraceStackTraceProxy<StackTraceElementStringProxy> retracedProxy, boolean verbose) {
     StringBuilder sb = new StringBuilder();
     int lastSeenIndex = 0;
-    if (retracedProxy.isAmbiguous() && printAmbiguous) {
-      lastSeenIndex = firstNonWhiteSpaceCharacterFromIndex(line, 0);
-      sb.append(line, 0, lastSeenIndex);
-      sb.append("<OR> ");
-    }
     for (StringIndex index : orderedIndices) {
       sb.append(line, lastSeenIndex, index.startIndex);
       sb.append(index.retracedString.apply(retracedProxy, this, verbose));
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 9061db8..632c74c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -361,7 +361,8 @@
 
   private void verify() {
     assert keepInfo.verifyPinnedTypesAreLive(liveTypes);
-    assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(liveTypes, this);
+    assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(
+        liveTypes, missingTypes, this);
   }
 
   private static KeepInfoCollection extendPinnedItems(
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 095ba07..e43e72a 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -116,6 +116,7 @@
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
 import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
@@ -2927,6 +2928,8 @@
       registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
     }
     if (mode.isInitialTreeShaking()) {
+      registerInvokeAnalysis(
+          appView.getInvokeSpecialBridgeSynthesizer().getEnqueuerInvokeAnalysis());
       // This is simulating the effect of the "root set" applied rules.
       // This is done only in the initial pass, in subsequent passes the "rules" are reapplied
       // by iterating the instances.
@@ -3144,6 +3147,7 @@
     // In particular these additions are order independent, i.e., it does not matter which are
     // registered first and no dependencies may exist among them.
     SyntheticAdditions additions = new SyntheticAdditions();
+    synthesizeInvokeSpecialBridges(additions);
     synthesizeInterfaceMethodBridges(additions);
     synthesizeLambdas(additions);
     synthesizeLibraryConversionWrappers(additions);
@@ -3169,6 +3173,12 @@
     additions.enqueueWorkItems(this);
   }
 
+  private void synthesizeInvokeSpecialBridges(SyntheticAdditions additions) {
+    SortedProgramMethodSet bridges =
+        appView.getInvokeSpecialBridgeSynthesizer().insertBridgesForR8();
+    bridges.forEach(additions::addLiveMethod);
+  }
+
   private void synthesizeInterfaceMethodBridges(SyntheticAdditions additions) {
     for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
       DexProgramClass holder = bridge.getHolder();
@@ -4431,14 +4441,11 @@
                     fieldReference, new FieldAccessInfoImpl(fieldReference));
         fieldAccessInfo.setReadFromAnnotation();
         markStaticFieldAsLive(field, KeepReason.referencedInAnnotation(annotationHolder));
-        // When an annotation has a field of an enum type with a default value then Java VM
-        // will use the values() method on that enum class.
-        if (options.isGeneratingClassFiles()
-            && annotationHolder == dexItemFactory.annotationDefault) {
-          if (field.getHolder().isEnum()) {
-            markEnumValuesAsReachable(
-                field.getHolder(), KeepReason.referencedInAnnotation(annotationHolder));
-          }
+        // When an annotation has a field of an enum type the JVM will use the values() method on
+        // that enum class if the field is referenced.
+        if (options.isGeneratingClassFiles() && field.getHolder().isEnum()) {
+          markEnumValuesAsReachable(
+              field.getHolder(), KeepReason.referencedInAnnotation(annotationHolder));
         }
       } else {
         // There is no dispatch on annotations, so only keep what is directly referenced.
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index d00bc9a..ae80f4c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -332,13 +332,15 @@
 
     @Override
     public boolean matches(DexType type) {
+      boolean lastWasNegated = false;
       for (Entry<ProguardTypeMatcher> className : classNames.object2BooleanEntrySet()) {
         if (className.getKey().matches(type)) {
           // If we match a negation, abort as non-match. If we match a positive, return true.
           return !className.getBooleanValue();
         }
+        lastWasNegated = className.getBooleanValue();
       }
-      return false;
+      return lastWasNegated;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanUtils.java b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
index 75d47f6..e99ff55 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
@@ -6,6 +6,8 @@
 public class BooleanUtils {
 
   private static final Boolean[] VALUES = new Boolean[] { Boolean.TRUE, Boolean.FALSE };
+  private static final Boolean[] TRUE_VALUES = new Boolean[] {Boolean.TRUE};
+  private static final Boolean[] FALSE_VALUES = new Boolean[] {Boolean.FALSE};
 
   public static int intValue(boolean value) {
     return value ? 1 : 0;
@@ -18,4 +20,12 @@
   public static Boolean[] values() {
     return VALUES;
   }
+
+  public static Boolean[] trueValues() {
+    return TRUE_VALUES;
+  }
+
+  public static Boolean[] falseValues() {
+    return FALSE_VALUES;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/Box.java b/src/main/java/com/android/tools/r8/utils/Box.java
index 8938bc8..77be1c9 100644
--- a/src/main/java/com/android/tools/r8/utils/Box.java
+++ b/src/main/java/com/android/tools/r8/utils/Box.java
@@ -34,4 +34,10 @@
   public boolean isSet() {
     return value != null;
   }
+
+  public T getAndSet(T newValue) {
+    T oldValue = value;
+    value = newValue;
+    return oldValue;
+  }
 }
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 dfbeff6..fe3388b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -508,6 +508,8 @@
   public boolean enableLambdaMerging = false;
   // Flag to turn on/off desugaring in D8/R8.
   public DesugarState desugarState = DesugarState.ON;
+  // Flag to turn on/off desugaring of invoke-special to a virtual method on the current class.
+  public boolean enableInvokeSpecialToVirtualMethodDesugaring = true;
   // Flag to turn on/off JDK11+ nest-access control
   public boolean enableNestBasedAccessDesugaring = true;
   // Flag to turn on/off reduction of nest to improve class merging optimizations.
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
index 15e1bec..4c2f3a8 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
@@ -52,4 +52,20 @@
     }
     return Collections.emptySet();
   }
+
+  @Override
+  public V remove(K key) {
+    V value = super.remove(key);
+    if (getKeys(value).size() <= 1 || getRepresentativeKey(value) == key) {
+      removeRepresentativeFor(value);
+    }
+    return value;
+  }
+
+  @Override
+  public Set<K> removeValue(V value) {
+    Set<K> keys = super.removeValue(value);
+    removeRepresentativeFor(value);
+    return keys;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index 7f04357..875e50d 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -182,6 +182,14 @@
     return self();
   }
 
+  public RR forCfRuntime(Consumer<RR> action) {
+    if (runtime.isCf()) {
+      action.accept(self());
+      executedSatisfyingRuntime = true;
+    }
+    return self();
+  }
+
   public RR otherwise(Consumer<RR> action) {
     if (!executedSatisfyingRuntime) {
       action.accept(self());
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 367a8f3..ad512cd 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -1287,7 +1288,8 @@
     D8Command command = builder.build();
     InternalOptions options = command.getInternalOptions();
     if (optionsConsumer != null) {
-      optionsConsumer.accept(options);
+      ExceptionUtils.withD8CompilationHandler(
+          options.reporter, () -> optionsConsumer.accept(options));
     }
     D8.runForTesting(command.getInputApp(), options);
     return compatSink.build();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
index 6e8f3e8..70ae6d6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
@@ -47,7 +47,7 @@
         .addOptionsModification(
             options ->
                 options.desugaredLibraryConfiguration =
-                    DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(x))
+                    DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(x, options))
         .compileWithExpectedDiagnostics(
             diagnostics -> {
               diagnostics.assertErrorMessageThatMatches(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
new file mode 100644
index 0000000..a9590a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -0,0 +1,730 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+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;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class ObjectsTest extends DesugaredLibraryTestBase implements Opcodes {
+
+  private final TestParameters parameters;
+  private final boolean libraryDesugarJavaUtilObjects;
+  private final boolean shrinkDesugaredLibrary = false;
+  private final Path androidJar;
+
+  @Parameters(name = "{0}, libraryDesugarJavaUtilObjects: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
+  }
+
+  public ObjectsTest(TestParameters parameters, boolean libraryDesugarJavaUtilObjects) {
+    this.parameters = parameters;
+    this.libraryDesugarJavaUtilObjects = libraryDesugarJavaUtilObjects;
+    // Using desugared library require a compile SDK of 26 or higher.
+    this.androidJar =
+        ToolHelper.getAndroidJar(Ordered.max(parameters.getApiLevel(), AndroidApiLevel.O));
+  }
+
+  DesugaredLibraryConfiguration desugaredLibraryConfiguration(
+      InternalOptions options, boolean libraryCompilation, TestParameters parameters) {
+    return new DesugaredLibraryConfigurationParser(
+            options.dexItemFactory(),
+            options.reporter,
+            libraryCompilation,
+            parameters.getApiLevel().getLevel())
+        .parse(
+            StringResource.fromFile(
+                libraryDesugarJavaUtilObjects
+                    ? ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING_ALTERNATIVE_3
+                    : ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+  }
+
+  private void configurationForProgramCompilation(InternalOptions options) {
+    options.desugaredLibraryConfiguration =
+        desugaredLibraryConfiguration(options, false, parameters);
+  }
+
+  private void configurationForLibraryCompilation(InternalOptions options) {
+    options.desugaredLibraryConfiguration =
+        desugaredLibraryConfiguration(options, true, parameters);
+  }
+
+  private Matcher<MethodSubject> invokesObjectsCompare(String holder) {
+    return invokesMethod(
+        "int",
+        holder,
+        "compare",
+        ImmutableList.of("java.lang.Object", "java.lang.Object", "java.util.Comparator"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsEquals(String holder) {
+    return invokesMethod(
+        "boolean", holder, "equals", ImmutableList.of("java.lang.Object", "java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsDeepEquals(String holder) {
+    return invokesMethod(
+        "boolean", holder, "deepEquals", ImmutableList.of("java.lang.Object", "java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsHash(String holder) {
+    return invokesMethod("int", holder, "hash", ImmutableList.of("java.lang.Object[]"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsHashCode(String holder) {
+    return invokesMethod("int", holder, "hashCode", ImmutableList.of("java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsRequireNonNull(String holder) {
+    return invokesMethod(
+        "java.lang.Object", holder, "requireNonNull", ImmutableList.of("java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsRequireNonNullWithMessage(String holder) {
+    return invokesMethod(
+        "java.lang.Object",
+        holder,
+        "requireNonNull",
+        ImmutableList.of("java.lang.Object", "java.lang.String"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsRequireNonNullWithSupplier(
+      String holder, String Supplier) {
+    return invokesMethod(
+        "java.lang.Object",
+        holder,
+        "requireNonNull",
+        ImmutableList.of("java.lang.Object", Supplier));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsToString(String holder) {
+    return invokesMethod(
+        "java.lang.String", holder, "toString", ImmutableList.of("java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsToStringWithNullDefault(String holder) {
+    return invokesMethod(
+        "java.lang.String",
+        holder,
+        "toString",
+        ImmutableList.of("java.lang.Object", "java.lang.String"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsIsNull(String holder) {
+    return invokesMethod("boolean", holder, "isNull", ImmutableList.of("java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsNonNull(String holder) {
+    return invokesMethod("boolean", holder, "nonNull", ImmutableList.of("java.lang.Object"));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClass = inspector.clazz(TestClass.class);
+    assertThat(testClass, isPresent());
+
+    // Objects.equals as added in Android K, so when backporting, this is only backported below K.
+    // However, for library desugaring, the desugaring of Objects.equals happens all the way up to
+    // Android M, as that is grouped with other methods like Objects.requireNonNull which was
+    // added in Android N.
+    boolean invokeJavaUtilObjects =
+        !libraryDesugarJavaUtilObjects
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)
+            || (libraryDesugarJavaUtilObjects
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    boolean invokeJDollarUtilObjects =
+        libraryDesugarJavaUtilObjects && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+    boolean invokeJavaUtilObjectsWithSupplier =
+        !libraryDesugarJavaUtilObjects || !parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+    boolean invokeJDollarUtilObjectsWithSupplier =
+        libraryDesugarJavaUtilObjects && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsCompare"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsCompare("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsCompare"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsCompare("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsDeepEquals"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsDeepEquals("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsDeepEquals"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsDeepEquals("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsEquals"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsEquals("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsEquals"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsEquals("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsHash"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsHash("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsHash"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsHash("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsHashCode"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsHashCode("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsHashCode"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsHashCode("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNull"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsRequireNonNull("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNull"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsRequireNonNull("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"),
+        onlyIf(
+            invokeJavaUtilObjects, invokesObjectsRequireNonNullWithMessage("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"),
+        onlyIf(
+            invokeJDollarUtilObjects, invokesObjectsRequireNonNullWithMessage("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNullWithSupplier"),
+        onlyIf(
+            invokeJavaUtilObjectsWithSupplier,
+            invokesObjectsRequireNonNullWithSupplier(
+                "java.util.Objects", "java.util.function.Supplier")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNullWithSupplier"),
+        onlyIf(
+            invokeJDollarUtilObjectsWithSupplier,
+            invokesObjectsRequireNonNullWithSupplier(
+                "j$.util.Objects", "j$.util.function.Supplier")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsToString"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsToString("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsToString"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsToString("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsToStringWithNullDefault"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsToStringWithNullDefault("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsToStringWithNullDefault"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsToStringWithNullDefault("j$.util.Objects")));
+
+    invokeJavaUtilObjects = parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsIsNull"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsIsNull("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsIsNull"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsIsNull("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsNonNull"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsNonNull("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsNonNull"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsNonNull("j$.util.Objects")));
+  }
+
+  @Test
+  public void testD8Cf() throws Exception {
+    // Adjust API level if running on JDK 8. The java.util.Objects methods added in
+    // Android R where added in JDK 9, so setting the the API level to Android P will backport
+    // these methods for JDK 8.
+    AndroidApiLevel apiLevel = parameters.getApiLevel();
+    if (parameters.getRuntime().isCf()
+        && parameters.getRuntime().asCf().getVm() == CfVm.JDK8
+        && apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.R)) {
+      apiLevel = AndroidApiLevel.P;
+    }
+
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addLibraryFiles(androidJar)
+            .addOptionsModification(this::configurationForProgramCompilation)
+            .addInnerClasses(ObjectsTest.class)
+            .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
+            .setMinApi(apiLevel)
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+
+    if (parameters.getRuntime().isDex()) {
+      // Collection keep rules is only implemented in the DEX writer.
+      String desugaredLibraryKeepRules = keepRuleConsumer.get();
+      if (desugaredLibraryKeepRules != null) {
+        assertEquals(0, desugaredLibraryKeepRules.length());
+        desugaredLibraryKeepRules = "-keep class * { *; }";
+      }
+
+      // Convert to DEX without desugaring and run.
+      testForD8()
+          .addLibraryFiles(androidJar)
+          .addProgramFiles(jar)
+          .setMinApi(apiLevel)
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              (apiLevel_, keepRules, shrink) ->
+                  buildDesugaredLibrary(
+                      apiLevel_,
+                      keepRules,
+                      shrink,
+                      ImmutableList.of(),
+                      this::configurationForLibraryCompilation),
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(
+              parameters.getRuntime(),
+              TestClass.class,
+              Boolean.toString(libraryDesugarJavaUtilObjects))
+          .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+    } else {
+      // Build the desugared library in class file format.
+      Path desugaredLib =
+          getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
+
+      // Run on the JVM with desuagred library on classpath.
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(desugaredLib)
+          .run(
+              parameters.getRuntime(),
+              TestClass.class,
+              Boolean.toString(libraryDesugarJavaUtilObjects))
+          .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+    }
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addLibraryFiles(androidJar)
+        .addOptionsModification(this::configurationForProgramCompilation)
+        .addInnerClasses(ObjectsTest.class)
+        .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (apiLevel, keepRules, shrink) ->
+                buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    this::configurationForLibraryCompilation),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .inspect(this::inspect)
+        .run(
+            parameters.getRuntime(),
+            TestClass.class,
+            Boolean.toString(libraryDesugarJavaUtilObjects))
+        .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(androidJar)
+        .addOptionsModification(this::configurationForProgramCompilation)
+        .addInnerClasses(ObjectsTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
+        .enableInliningAnnotations()
+        .noMinification()
+        .addKeepRules("-keep class AndroidRUtilsObjectsMethods { *; }")
+        .addKeepRules("-neverinline class AndroidRUtilsObjectsMethods { *; }")
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (apiLevel, keepRules, shrink) ->
+                buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    this::configurationForLibraryCompilation),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .inspect(this::inspect)
+        .run(
+            parameters.getRuntime(),
+            TestClass.class,
+            Boolean.toString(libraryDesugarJavaUtilObjects))
+        .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+  }
+
+  private String expectedOutput(boolean objectsRequireNonNullWithSupplierSupported) {
+    return StringUtils.lines(
+        "1",
+        "true",
+        "true",
+        Objects.toString(Objects.hash(1, 2)),
+        "4",
+        "NPE",
+        "Was null",
+        objectsRequireNonNullWithSupplierSupported
+            ? "Supplier said was null"
+            : "Not supported (b/174840626)",
+        "5",
+        "6",
+        "true",
+        "false",
+        "1",
+        "2",
+        "3",
+        "4");
+  }
+
+  static class TestClass {
+    @NeverInline
+    private static void objectsCompare(String s1, String s2) {
+      Comparator<String> stringsNullLast =
+          (o1, o2) -> {
+            if (o1 == null) {
+              return o2 == null ? 0 : 1;
+            }
+            return o2 == null ? -1 : o1.compareTo(o2);
+          };
+      System.out.println(Objects.compare(s1, s2, stringsNullLast));
+    }
+
+    @NeverInline
+    private static void objectsDeepEquals(Object o1, Object o2) {
+      System.out.println(Objects.deepEquals(o1, o2));
+    }
+
+    @NeverInline
+    private static void objectsEquals(Object o1, Object o2) {
+      System.out.println(Objects.equals(o1, o2));
+    }
+
+    @NeverInline
+    private static void objectsHash(Object o1, Object o2) {
+      System.out.println(Objects.hash(o1, o2));
+    }
+
+    @NeverInline
+    private static void objectsHashCode(Object o) {
+      System.out.println(Objects.hashCode(o));
+    }
+
+    @NeverInline
+    private static void objectsRequireNonNull(Object o) {
+      try {
+        System.out.println(Objects.requireNonNull(o));
+      } catch (NullPointerException e) {
+        System.out.println("NPE");
+      }
+    }
+
+    @NeverInline
+    private static void objectsRequireNonNullWithMessage(Object o, String message) {
+      try {
+        System.out.println(Objects.requireNonNull(o, message));
+      } catch (NullPointerException e) {
+        System.out.println(e.getMessage());
+      }
+    }
+
+    @NeverInline
+    private static void objectsRequireNonNullWithSupplier(
+        Object o, Supplier<String> messageSupplier) {
+      try {
+        System.out.println(Objects.requireNonNull(o, messageSupplier));
+      } catch (NullPointerException e) {
+        System.out.println(e.getMessage());
+      }
+    }
+
+    @NeverInline
+    private static void objectsToString(Object o) {
+      System.out.println(Objects.toString(o));
+    }
+
+    @NeverInline
+    private static void objectsToStringWithNullDefault(Object o, String nullDefault) {
+      System.out.println(Objects.toString(o, nullDefault));
+    }
+
+    @NeverInline
+    private static void objectsIsNull(Object o) {
+      System.out.println(Objects.isNull(o));
+    }
+
+    @NeverInline
+    private static void objectsNonNull(Object o) {
+      System.out.println(Objects.nonNull(o));
+    }
+
+    public static void main(String[] args) throws Exception {
+      boolean objectsRequireNonNullWithSupplierSupported = Boolean.parseBoolean(args[0]);
+      // Android K methods.
+      objectsCompare("b", "a");
+      objectsDeepEquals(args, args);
+      objectsEquals(args, args);
+      objectsHash(1, 2);
+      objectsHashCode(4);
+      objectsRequireNonNull(null);
+      objectsRequireNonNullWithMessage(null, "Was null");
+      if (objectsRequireNonNullWithSupplierSupported) {
+        objectsRequireNonNullWithSupplier(null, () -> "Supplier said was null");
+      } else {
+        System.out.println("Not supported (b/174840626)");
+      }
+      objectsToString("5");
+      objectsToStringWithNullDefault(null, "6");
+
+      // Android N methods.
+      objectsIsNull(null);
+      objectsNonNull(null);
+
+      // Android R methods.
+      Class<?> c = Class.forName("AndroidRUtilsObjectsMethods");
+      c.getDeclaredMethod("checkFromIndexSize", int.class, int.class, int.class)
+          .invoke(null, 1, 2, 10);
+      c.getDeclaredMethod("checkFromToIndex", int.class, int.class, int.class)
+          .invoke(null, 2, 4, 10);
+      c.getDeclaredMethod("checkIndex", int.class, int.class).invoke(null, 3, 10);
+      c.getDeclaredMethod("requireNonNullElse", Object.class, Object.class).invoke(null, null, 4);
+      // TODO(b/174840626) Also support requireNonNullElseGet.
+    }
+  }
+
+  /*
+    Dump below is from this source:
+
+    import java.util.function.Supplier;
+    import java.util.Objects;
+
+    public class AndroidRUtilsObjectsMethods {
+      public static void checkFromIndexSize(int fromIndex, int size, int length) {
+        System.out.println(Objects.checkFromIndexSize(fromIndex, size, length));
+      }
+      public static void checkFromToIndex(int fromIndex, int toIndex, int length) {
+        System.out.println(Objects.checkFromToIndex(fromIndex, toIndex, length));
+      }
+      public static void checkIndex(int index, int length) {
+        System.out.println(Objects.checkIndex(index, length));
+      }
+      public static <T> void requireNonNullElse(T obj, T defaultObj) {
+        System.out.println(Objects.requireNonNullElse(obj, defaultObj));
+      }
+      public static <T> void requireNonNullElseGet(T obj, Supplier<? extends T> supplier) {
+        System.out.println(Objects.requireNonNullElse(obj, supplier));
+      }
+    }
+
+    This is added as a dump as it use APIs which are only abailable from JDK 9.
+  */
+  public static byte[] dumpAndroidRUtilsObjectsMethods() throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V9, ACC_PUBLIC | ACC_SUPER, "AndroidRUtilsObjectsMethods", null, "java/lang/Object", null);
+
+    classWriter.visitSource("AndroidRUtilsObjectsMethods.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(3, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC, "checkFromIndexSize", "(III)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(5, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ILOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitVarInsn(ILOAD, 2);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "java/util/Objects", "checkFromIndexSize", "(III)I", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(6, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(4, 3);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC, "checkFromToIndex", "(III)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(8, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ILOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitVarInsn(ILOAD, 2);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "java/util/Objects", "checkFromToIndex", "(III)I", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(9, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(4, 3);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "checkIndex", "(II)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(11, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ILOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "java/util/Objects", "checkIndex", "(II)I", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(12, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC,
+              "requireNonNullElse",
+              "(Ljava/lang/Object;Ljava/lang/Object;)V",
+              "<T:Ljava/lang/Object;>(TT;TT;)V",
+              null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(14, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "java/util/Objects",
+          "requireNonNullElse",
+          "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+          false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(15, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC,
+              "requireNonNullElseGet",
+              "(Ljava/lang/Object;Ljava/util/function/Supplier;)V",
+              "<T:Ljava/lang/Object;>(TT;Ljava/util/function/Supplier<+TT;>;)V",
+              null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(18, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "java/util/Objects",
+          "requireNonNullElse",
+          "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+          false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(19, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 2);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassRecursionTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassRecursionTest.java
new file mode 100644
index 0000000..3ef9e29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassRecursionTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+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 InvokeSpecialOnSameClassRecursionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialOnSameClassRecursionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addProgramClasses(Main.class, B.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.foo -1");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, B.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.foo -1");
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(A.class)
+        .transformMethodInsnInMethod(
+            "foo",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.equals("foo")) {
+                continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
+              } else {
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  static class A {
+    private int i = 5;
+
+    public void foo() {
+      if (i-- > 0) {
+        foo();
+      } else {
+        System.out.println("A.foo " + i);
+      }
+    }
+  }
+
+  static class B extends A {
+    public void run() {
+      super.foo();
+    }
+
+    public void foo() {
+      throw new RuntimeException("never called");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().run();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
index 48790e3..ffb5f37 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
@@ -4,12 +4,8 @@
 
 package com.android.tools.r8.graph.invokespecial;
 
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -35,24 +31,15 @@
 
   @Test
   public void testRuntime() throws Exception {
-    try {
-      testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
-          .addProgramClasses(Main.class)
-          .addProgramClassFileData(getClassWithTransformedInvoked())
-          .run(parameters.getRuntime(), Main.class)
-          .assertSuccessWithOutputLines("Hello World!");
-      // TODO(b/110175213): Remove when fixed.
-      assertTrue(parameters.isCfRuntime());
-    } catch (CompilationFailedException compilation) {
-      assertThat(
-          compilation.getCause().getMessage(),
-          containsString("Failed to compile unsupported use of invokespecial"));
-    }
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
   }
 
   @Test
   public void testR8() throws Exception {
-    try {
       testForR8(parameters.getBackend())
           .addProgramClasses(Main.class)
           .addProgramClassFileData(getClassWithTransformedInvoked())
@@ -60,13 +47,6 @@
           .setMinApi(parameters.getApiLevel())
           .run(parameters.getRuntime(), Main.class)
           .assertSuccessWithOutputLines("Hello World!");
-      // TODO(b/110175213): Remove when fixed.
-      assertTrue(parameters.isCfRuntime());
-    } catch (CompilationFailedException compilation) {
-      assertThat(
-          compilation.getCause().getMessage(),
-          containsString("Failed to compile unsupported use of invokespecial"));
-    }
   }
 
   private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameInterfaceTest.java
new file mode 100644
index 0000000..f57ce9b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameInterfaceTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+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 InvokeSpecialOnSameInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialOnSameInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addProgramClasses(Main.class, A.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, A.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(AInterface.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public interface AInterface {
+    default void foo() {
+      System.out.println("Hello World!");
+    }
+
+    default void bar() {
+      foo(); // Will be rewritten to invoke-special AInterface.foo()
+    }
+  }
+
+  public static class A implements AInterface {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialSuperVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialSuperVirtualTest.java
new file mode 100644
index 0000000..012dc0d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialSuperVirtualTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+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 InvokeSpecialSuperVirtualTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("A", "B", "A", "C", "A", "B", "A", "B", "A");
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialSuperVirtualTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addProgramClasses(A.class, C.class, Main.class)
+        .addProgramClassFileData(getClassBWithTransformedInvoked())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, C.class, Main.class)
+        .addProgramClassFileData(getClassBWithTransformedInvoked())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private byte[] getClassBWithTransformedInvoked() throws IOException {
+    return transformer(B.class)
+        .transformMethodInsnInMethod(
+            "printAll",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.equals("replace")) {
+                assertEquals(INVOKESPECIAL, opcode);
+                assertEquals(getBinaryNameFromJavaType(B.class.getTypeName()), owner);
+                continuation.visitMethodInsn(opcode, owner, "print", descriptor, isInterface);
+              } else {
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    void print() {
+      System.out.println("A");
+    }
+  }
+
+  public static class B extends A {
+
+    void print() {
+      System.out.println("B");
+      super.print();
+    }
+
+    private void replace() {
+      System.out.println("Should not be called.");
+    }
+
+    void printAll() {
+      // Invoke-super.
+      super.print();
+      // Rewritten to Invoke-special to B::print.
+      replace();
+      // Invoke-virtual.
+      print();
+    }
+  }
+
+  public static class C extends B {
+
+    void print() {
+      System.out.println("C");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new C().printAll();
+      new B().printAll();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
new file mode 100644
index 0000000..b439787
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToSubclassTest extends TestBase {
+
+  private static final Set<Class<?>> CLASSES_TO_TEST =
+      ImmutableSet.of(C.class, EmptySubC.class, D.class);
+
+  private final TestParameters parameters;
+  private final Class<?> holder;
+
+  @Parameters(name = "{0} class: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), CLASSES_TO_TEST);
+  }
+
+  public InvokeSpecialToSubclassTest(TestParameters parameters, Class<?> holder) {
+    this.parameters = parameters;
+    this.holder = holder;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addProgramClasses(EmptySubC.class, C.class, Main.class)
+        .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
+        .run(parameters.getRuntime(), Main.class)
+        // The failures are very different from one back-end to another: verification error,
+        // invalid invoke-super, segmentation fault, NoSuchMethod, etc.
+        .assertFailure();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(EmptySubC.class, C.class, Main.class)
+        .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        // The failures are very different from one back-end to another: verification error,
+        // invalid invoke-super, segmentation fault, NoSuchMethod, etc.
+        .assertFailure();
+  }
+
+  private byte[] getClassBWithTransformedInvoked(Class<?> holder) throws IOException {
+    return transformer(B.class)
+        .transformMethodInsnInMethod(
+            "callPrint",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.equals("replace")) {
+                assertEquals(INVOKESPECIAL, opcode);
+                assertEquals(getBinaryNameFromJavaType(B.class.getTypeName()), owner);
+                continuation.visitMethodInsn(
+                    opcode,
+                    getBinaryNameFromJavaType(holder.getTypeName()),
+                    "print",
+                    descriptor,
+                    isInterface);
+              } else {
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  public static class B {
+
+    void print() {
+      System.out.println("B");
+    }
+
+    void callPrint() {
+      ((C) this).replace(); // Replaced by C, EmptySubC and D invoke-special to print.
+    }
+  }
+
+  public static class C extends B {
+
+    private void replace() {
+      System.out.println("Should not be called.");
+    }
+
+    @Override
+    void print() {
+      System.out.println("C");
+    }
+  }
+
+  public static class EmptySubC extends C {}
+
+  public static class D extends EmptySubC {
+    @Override
+    void print() {
+      System.out.println("C");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new D().callPrint();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSuperclassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSuperclassTest.java
new file mode 100644
index 0000000..90c61eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSuperclassTest.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToSuperclassTest extends TestBase {
+
+  private static final Set<Class<?>> CLASSES_TO_TEST =
+      ImmutableSet.of(S.class, A.class, EmptySubA.class);
+
+  private final TestParameters parameters;
+  private final Class<?> holder;
+
+  @Parameters(name = "{0} class: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), CLASSES_TO_TEST);
+  }
+
+  public InvokeSpecialToSuperclassTest(TestParameters parameters, Class<?> holder) {
+    this.parameters = parameters;
+    this.holder = holder;
+  }
+
+  public String getExpectedResult(boolean r8) {
+    if (holder == S.class
+        && parameters.isDexRuntime()
+        && !r8
+        && parameters.getDexRuntimeVersion().isNewerThanOrEqual(ToolHelper.DexVm.Version.V5_1_1)
+        && parameters.getDexRuntimeVersion().isOlderThanOrEqual(ToolHelper.DexVm.Version.V6_0_1)) {
+      // TODO(b/175285016): Should be "A".
+      // On Android 5-6, the result for S.class is "S" instead of "A" with D8 compilation.
+      return "S";
+    }
+    return "A";
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addProgramClasses(S.class, A.class, EmptySubA.class, Main.class)
+        .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedResult(false));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(S.class, A.class, EmptySubA.class, Main.class)
+        .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedResult(true));
+  }
+
+  private byte[] getClassBWithTransformedInvoked(Class<?> holder) throws IOException {
+    return transformer(B.class)
+        .transformMethodInsnInMethod(
+            "callPrint",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.equals("replace")) {
+                assertEquals(INVOKESPECIAL, opcode);
+                assertEquals(getBinaryNameFromJavaType(B.class.getTypeName()), owner);
+                continuation.visitMethodInsn(
+                    opcode,
+                    getBinaryNameFromJavaType(holder.getTypeName()),
+                    "print",
+                    descriptor,
+                    isInterface);
+              } else {
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  public static class S {
+
+    void print() {
+      System.out.println("S");
+    }
+  }
+
+  public static class A extends S {
+
+    void print() {
+      System.out.println("A");
+    }
+  }
+
+  public static class EmptySubA extends A {}
+
+  public static class B extends EmptySubA {
+
+    void print() {
+      System.out.println("B");
+    }
+
+    private void replace() {
+      System.out.println("Should not be called.");
+    }
+
+    void callPrint() {
+      replace(); // Replaced by S, A, EmptySubA invoke-special to print.
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().callPrint();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
index 400e015..830283a 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
@@ -3,13 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph.invokespecial;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -47,35 +45,26 @@
         .assertSuccessWithOutput(EXPECTED);
   }
 
-
-  @Test(expected = CompilationFailedException.class)
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     testForD8()
         .addProgramClasses(Base.class, Bar.class, TestClass.class)
         .addProgramClassFileData(getFooTransform())
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics ->
-                diagnostics
-                    .assertOnlyErrors()
-                    .assertErrorsMatch(
-                        diagnosticMessage(containsString("unsupported use of invokespecial"))));
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(Base.class, Bar.class, TestClass.class)
         .addProgramClassFileData(getFooTransform())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(TestClass.class)
-        .compileWithExpectedDiagnostics(
-            diagnostics ->
-                diagnostics
-                    .assertOnlyErrors()
-                    .assertErrorsMatch(
-                        diagnosticMessage(containsString("unsupported use of invokespecial"))));
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
index 44dc04c..c329d52 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
@@ -5,7 +5,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.google.common.collect.ImmutableList;
 import org.junit.Rule;
 import org.junit.Test;
@@ -71,10 +70,6 @@
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
 
-    thrown.expect(CompilationFailedException.class);
-
-    // TODO(b/110175213): This will fail with a compilation exception since we cannot translate
-    //  an invoke-special to a member on the same class.
     String artResult = runOnArtD8(builder, clazz.name);
     assertEquals(expected, artResult);
   }
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
index c81a2d6..3883528 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
@@ -78,7 +78,8 @@
                 options ->
                     options.desugaredLibraryConfiguration =
                         DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(
-                            ImmutableMap.of(packageName + ".runtime", packageName + ".library")))
+                            ImmutableMap.of(packageName + ".runtime", packageName + ".library"),
+                            options))
             .compile();
 
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java
new file mode 100644
index 0000000..e589ab1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, 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.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithStringIdentifier extends RepackageTestBase {
+
+  public final String EXPECTED = "Hello World!";
+
+  public RepackageWithStringIdentifier(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, ACaller.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(A.class).removeInnerClasses().transform(),
+            transformer(ACaller.class).removeInnerClasses().transform(),
+            transformer(Main.class).removeInnerClasses().transform())
+        .apply(this::configureRepackaging)
+        .addKeepRules(
+            "-identifiernamestring class "
+                + Main.class.getTypeName()
+                + " { java.lang.String name; }")
+        .addKeepRules("-keepclassmembers,allowshrinking class **")
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> assertThat(A.class, isRepackaged(inspector)))
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(IllegalAccessException.class);
+  }
+
+  @NeverClassInline
+  static class A {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class ACaller {
+
+    @NeverInline
+    public static void callA() {
+      new A().foo();
+    }
+  }
+
+  public static class Main {
+
+    private static String name;
+
+    static {
+      if (System.currentTimeMillis() > 0) {
+        name = "com.android.tools.r8.repackage.RepackageWithStringIdentifier$A";
+      }
+    }
+
+    public static void main(String[] args) throws Exception {
+      Class.forName(name).getDeclaredConstructor().newInstance();
+      ACaller.callA();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 0c7dd7a..72221e3 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -34,10 +34,12 @@
 import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleDotsInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
+import com.android.tools.r8.retrace.stacktraces.NoObfuscationRangeMappingWithStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
 import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
+import com.android.tools.r8.retrace.stacktraces.SourceFileWithNumberAndEmptyStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
 import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
 import com.android.tools.r8.retrace.stacktraces.UnicodeInFileNameStackTrace;
@@ -93,6 +95,11 @@
   }
 
   @Test
+  public void testNoObfuscationRangeMappingWithStackTrace() {
+    runRetraceTest(new NoObfuscationRangeMappingWithStackTrace());
+  }
+
+  @Test
   public void testNullLineTrace() {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     NullStackTrace nullStackTrace = new NullStackTrace();
@@ -217,6 +224,11 @@
     inspectRetraceTest(stackTraceForTest, stackTraceForTest::inspectField);
   }
 
+  @Test
+  public void testSourceFileWithNumberAndEmptyStackTrace() {
+    runRetraceTest(new SourceFileWithNumberAndEmptyStackTrace());
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
index ddd4b92..4543fab 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
@@ -32,9 +32,7 @@
     return Arrays.asList(
         "java.lang.IndexOutOfBoundsException",
         "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.foo(Internal.java)",
-        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)",
-        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)");
+        "\tat com.android.tools.r8.Internal.foo(Internal.java)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
index aa5946c..e4dc987 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
@@ -33,10 +33,7 @@
     return Arrays.asList(
         "java.lang.IndexOutOfBoundsException",
         "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.foo(Internal.java)",
-        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)",
-        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)",
-        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)");
+        "\tat com.android.tools.r8.Internal.foo(Internal.java)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
new file mode 100644
index 0000000..4057a91
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class NoObfuscationRangeMappingWithStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat foo.a(Bar.dummy:0)",
+        "\tat foo.b(Foo.dummy:2)",
+        "\tat foo.c(Baz.dummy:8)",
+        "\tat foo.d(Qux.dummy:7)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.foo(Main.dummy:1)",
+        "\tat com.android.tools.r8.naming.retrace.Main.bar(Main.dummy:3)",
+        "\tat com.android.tools.r8.naming.retrace.Main.baz(Main.dummy:8)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:7)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> foo:",
+        "    void foo(long):1:1 -> a",
+        "    void bar(int):3 -> b",
+        "    void baz():0:0 -> c", // For 0:0 and 0 use the original line number
+        "    void main(java.lang.String[]):0 -> d");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
new file mode 100644
index 0000000..8a94018
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class SourceFileWithNumberAndEmptyStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "  at com.android.tools.r8.R8.a(R.java:34)", "  at com.android.tools.r8.R8.a(:34)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.R8 -> com.android.tools.r8.R8:",
+        "  34:34:void com.android.tools.r8.utils.ExceptionUtils.withR8CompilationHandler("
+            + "com.android.tools.r8.utils.Reporter,"
+            + "com.android.tools.r8.utils.ExceptionUtils$CompileAction):59:59 -> a",
+        "  34:34:void runForTesting(com.android.tools.r8.utils.AndroidApp,"
+            + "com.android.tools.r8.utils.InternalOptions):261 -> a");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "  at com.android.tools.r8.utils.ExceptionUtils.withR8CompilationHandler("
+            + "ExceptionUtils.java:59)",
+        "  at com.android.tools.r8.R8.runForTesting(R.java:261)",
+        "  at com.android.tools.r8.utils.ExceptionUtils.withR8CompilationHandler("
+            + "ExceptionUtils.java:59)",
+        "  at com.android.tools.r8.R8.runForTesting(R8.java:261)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
index 3da5958..7254ee6 100644
--- a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
@@ -4,115 +4,97 @@
 package com.android.tools.r8.shaking;
 
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
-import java.util.List;
-import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-interface GoingToBeMissed {
-  void onSomeEvent(long soLong);
-}
-
-class TestClassForB112849320 {
-  GoingToBeMissed instance;
-  void foo(GoingToBeMissed instance) {
-    System.out.println("B112849320");
-    this.instance = instance;
-  }
-
-  void bar() {
-    instance.onSomeEvent(8L);
-  }
-
-  public static void main(String[] args) {
-    TestClassForB112849320 self = new TestClassForB112849320();
-    self.foo(l -> {
-      if (l > 0) {
-        System.out.println(l);
-      }
-    });
-    self.bar();
-  }
-}
-
+@RunWith(Parameterized.class)
 public class MissingInterfaceTest extends TestBase {
-  private static String MAIN_NAME = TestClassForB112849320.class.getCanonicalName();
-  private Path libJar;
-  private Path libDex;
 
-  @Before
-  public void setUp() throws Exception {
-    libJar = writeToJar(ImmutableList.of(ToolHelper.getClassAsBytes(GoingToBeMissed.class)));
-    libDex = temp.getRoot().toPath().resolve("lib.zip");
-    AndroidApp libApp = ToolHelper.runD8(readClasses(GoingToBeMissed.class));
-    libApp.writeToZip(libDex, OutputMode.DexIndexed);
+  private final TestParameters parameters;
+  private final String[] EXPECTED = new String[] {"B112849320", "8"};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MissingInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
   public void test_missingInterface() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-printmapping",
-        "-keep class " + MAIN_NAME + " {",
-        "  public static void main(...);",
-        "}"
-    );
-    R8Command.Builder builder = R8Command.builder();
-    builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClassForB112849320.class));
-    builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
-    builder.addProguardConfiguration(config, Origin.unknown());
-    AndroidApp processedApp = ToolHelper.runR8(builder.build(), options -> {
-      options.enableInlining = false;
-    });
-
-    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
-    processedApp.writeToZip(outDex, OutputMode.DexIndexed);
-    ProcessResult artResult = ToolHelper.runArtRaw(
-        ImmutableList.of(outDex.toString(), libDex.toString()), MAIN_NAME, null);
-    assertNotEquals(0, artResult.exitCode);
-    assertThat(artResult.stdout, containsString("B112849320"));
-    assertNotEquals(-1, artResult.stderr.indexOf("AbstractMethodError"));
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClassForB112849320.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClassForB112849320.class)
+        .addOptionsModification(options -> options.enableInlining = false)
+        .allowDiagnosticWarningMessages(
+            parameters.isCfRuntime() || !parameters.canUseDefaultAndStaticInterfaceMethods())
+        .compile()
+        .addRunClasspathFiles(
+            buildOnDexRuntime(
+                parameters,
+                writeToJar(ImmutableList.of(ToolHelper.getClassAsBytes(GoingToBeMissed.class)))))
+        .run(parameters.getRuntime(), TestClassForB112849320.class)
+        .forCfRuntime(r -> r.assertSuccessWithOutputLines(EXPECTED))
+        .otherwise(
+            r -> {
+              r.assertStdoutMatches(containsString("B112849320"));
+              r.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+            });
   }
 
+
   @Test
   public void test_passingInterfaceAsLib() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-printmapping",
-        "-keep class " + MAIN_NAME + " {",
-        "  public static void main(...);",
-        "}"
-    );
-    R8Command.Builder builder = R8Command.builder();
-    builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClassForB112849320.class));
-    builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar(), libJar);
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
-    builder.addProguardConfiguration(config, Origin.unknown());
-    AndroidApp processedApp = ToolHelper.runR8(builder.build(), options -> {
-      options.enableInlining = false;
-    });
-
-    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
-    processedApp.writeToZip(outDex, OutputMode.DexIndexed);
-    ProcessResult artResult = ToolHelper.runArtRaw(
-        ImmutableList.of(outDex.toString(), libDex.toString()), MAIN_NAME, null);
-    assertEquals(0, artResult.exitCode);
-    assertThat(artResult.stdout, containsString("B112849320"));
-    assertEquals(-1, artResult.stderr.indexOf("AbstractMethodError"));
+    Path lib = writeToJar(ImmutableList.of(ToolHelper.getClassAsBytes(GoingToBeMissed.class)));
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClassForB112849320.class)
+        .addLibraryFiles(lib)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClassForB112849320.class)
+        .addOptionsModification(options -> options.enableInlining = false)
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, lib))
+        .run(parameters.getRuntime(), TestClassForB112849320.class)
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
+  interface GoingToBeMissed {
+    void onSomeEvent(long soLong);
+  }
+
+  public static class TestClassForB112849320 {
+    GoingToBeMissed instance;
+
+    void foo(GoingToBeMissed instance) {
+      System.out.println("B112849320");
+      this.instance = instance;
+    }
+
+    void bar() {
+      instance.onSomeEvent(8L);
+    }
+
+    public static void main(String[] args) {
+      TestClassForB112849320 self = new TestClassForB112849320();
+      self.foo(
+          l -> {
+            if (l > 0) {
+              System.out.println(l);
+            }
+          });
+      self.bar();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
index 983928b..f0152a6 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
@@ -95,7 +95,7 @@
     assertTrue(matchClassName("boobar", "!foobar", "?*<1>ar"));
     assertFalse(matchClassName("foobar", "!foobar", "*bar"));
 
-    assertFalse(matchClassName("foo", "!boo"));
+    assertTrue(matchClassName("foo", "!boo"));
     assertFalse(matchClassName("foo", "baz,!boo"));
 
     assertFalse(matchClassName("boo", "!boo", "**"));
@@ -105,7 +105,8 @@
     assertTrue(matchClassName("boo",
         ImmutableList.of(ImmutableList.of("!boo"), ImmutableList.of("**"))));
 
-    assertFalse(matchClassName("boofoo", "!boo*,*foo,boofoo"));
+    // TODO(b/174824047): This parses as !(boo*,*foo,boofoo) and it should be !boo*,*foo,boofoo.
+    assertTrue(matchClassName("boofoo", "!boo*,*foo,boofoo"));
     assertTrue(matchClassName("boofoo",
         ImmutableList.of(ImmutableList.of("!boo*,*foo"), ImmutableList.of("boofoo"))));
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
index acb1e86..3b9c067 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
@@ -51,13 +51,9 @@
     //   * REQUIRED: annotation for Test.field1
     //   * OPTIONAL: default value of WireField.label
     // When generating class file the field values[] is also present as values() is kept.
-    assertEquals(
-        parameters.isCfRuntime() && defaultEnumValueInAnnotation ? 3 : 2,
-        classSubject.allFields().size());
+    assertEquals(parameters.isCfRuntime() ? 3 : 2, classSubject.allFields().size());
     // Methods <clinit>, <init> always present. values() present if generating class file.
-    assertEquals(
-        parameters.isCfRuntime() && defaultEnumValueInAnnotation ? 3 : 2,
-        classSubject.allMethods().size());
+    assertEquals(parameters.isCfRuntime() ? 3 : 2, classSubject.allMethods().size());
   }
 
   @Test
@@ -254,7 +250,7 @@
     return classWriter.toByteArray();
   }
 
-  public static byte[] classWireFieldLabel() throws Exception {
+  public static byte[] classWireFieldLabel() {
 
     ClassWriter classWriter = new ClassWriter(0);
     FieldVisitor fieldVisitor;
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
new file mode 100644
index 0000000..d42d3ad
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.enums;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+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 EnumInAnnotationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EnumInAnnotationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(EnumInAnnotationTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("TEST_ONE");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumInAnnotationTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRuntimeVisibleAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("TEST_ONE");
+  }
+
+  public enum Enum {
+    TEST_ONE,
+    TEST_TWO
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  public @interface MyAnnotation {
+
+    Enum value();
+  }
+
+  @MyAnnotation(value = Enum.TEST_ONE)
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(Main.class.getAnnotation(MyAnnotation.class).value());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java b/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
new file mode 100644
index 0000000..a0f56b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
@@ -0,0 +1,295 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.negatedrules;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.Subject;
+import org.hamcrest.Matcher;
+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 NegatedKeepRulesTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntime(CfVm.JDK9).withDexRuntime(Version.DEFAULT).build();
+  }
+
+  public NegatedKeepRulesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testPlainR8Compat() throws Exception {
+    testPlain(testForR8Compat(parameters.getBackend()));
+  }
+
+  @Test
+  public void testPlainR8Full() throws Exception {
+    testPlain(testForR8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testPlainProguard() throws Exception {
+    testPlain(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+  }
+
+  public void testPlain(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    run(testBuilder.addKeepRules("-keep class " + A.class.getTypeName() + " { *; }"))
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), not(isPresent()));
+              assertThat(inspector.clazz(C.class), not(isPresent()));
+              assertThat(inspector.clazz(D.class), not(isPresent()));
+              assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+              assertThat(inspector.clazz(BarBar.class), not(isPresent()));
+            });
+  }
+
+  @Test
+  public void testNegationR8Compat() throws Exception {
+    testNegation(testForR8Compat(parameters.getBackend()));
+  }
+
+  @Test
+  public void testNegationPlainR8Full() throws Exception {
+    testNegation(testForR8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testNegationProguard() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testNegation(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+  }
+
+  public void testNegation(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    run(testBuilder.addKeepRules("-keep class !" + B.class.getTypeName() + " { *; }"))
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), not(isPresent()));
+              assertThat(inspector.clazz(C.class), isPresent());
+              assertThat(inspector.clazz(D.class), isPresent());
+              assertThat(inspector.clazz(FooBar.class), isPresent());
+              assertThat(inspector.clazz(BarBar.class), isPresent());
+            });
+  }
+
+  @Test
+  public void testExtendsR8Compat() throws Exception {
+    testExtends(testForR8Compat(parameters.getBackend()));
+  }
+
+  @Test
+  public void testExtendsR8Full() throws Exception {
+    testExtends(testForR8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testExtendsProguard() throws Exception {
+    testExtends(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+  }
+
+  public void testExtends(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    run(testBuilder.addKeepRules("-keep class ** extends " + A.class.getTypeName() + " { *; }"))
+        .inspect(
+            inspector -> {
+              // A is only kept in full-mode because we are keeping two sub-types. For full-mode,
+              // A could be removed. This is shown in the testNegatedExtends test.
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), isPresent());
+              assertThat(inspector.clazz(C.class), not(isPresent()));
+              assertThat(inspector.clazz(D.class), isPresent());
+              assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+              assertThat(inspector.clazz(BarBar.class), not(isPresent()));
+            });
+  }
+
+  @Test
+  public void testNegatedExtendsR8Compat() throws Exception {
+    testNegatedExtends(testForR8Compat(parameters.getBackend()), isPresent());
+  }
+
+  @Test
+  public void testNegatedExtendsR8Full() throws Exception {
+    testNegatedExtends(testForR8(parameters.getBackend()), not(isPresent()));
+  }
+
+  @Test
+  public void testNegatedExtendsProguard() throws Exception {
+    testNegatedExtends(
+        testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"), isPresent());
+  }
+
+  public void testNegatedExtends(
+      TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder, Matcher<Subject> aMatcher) throws Exception {
+    // The negation binds closer than extends (at least for us).
+    run(testBuilder.addKeepRules("-keep class !**B extends " + A.class.getTypeName() + " { *; }"))
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), aMatcher);
+              assertThat(inspector.clazz(B.class), not(isPresent()));
+              assertThat(inspector.clazz(C.class), not(isPresent()));
+              assertThat(inspector.clazz(D.class), isPresent());
+              assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+              assertThat(inspector.clazz(BarBar.class), not(isPresent()));
+            });
+  }
+
+  @Test
+  public void testNegatedWithStarsR8Compat() throws Exception {
+    testNegatedWithStars(testForR8Compat(parameters.getBackend()));
+  }
+
+  @Test
+  public void testNegatedWithStarsR8Full() throws Exception {
+    testNegatedWithStars(testForR8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testNegatedWithStarsProguard() throws Exception {
+    testNegatedWithStars(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+  }
+
+  public void testNegatedWithStars(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    run(testBuilder.addKeepRules("-keep class !" + B.class.getTypeName() + ", ** { *; }"))
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), not(isPresent()));
+              assertThat(inspector.clazz(C.class), isPresent());
+              assertThat(inspector.clazz(D.class), isPresent());
+              assertThat(inspector.clazz(FooBar.class), isPresent());
+              assertThat(inspector.clazz(BarBar.class), isPresent());
+            });
+  }
+
+  @Test
+  public void testMultipleNegatedWithStarsR8Compat() throws Exception {
+    testMultipleNegatedWithStars(testForR8Compat(parameters.getBackend()));
+  }
+
+  @Test
+  public void testMultipleNegatedWithStarsR8Full() throws Exception {
+    testMultipleNegatedWithStars(testForR8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testMultipleWithStarsProguard() throws Exception {
+    testMultipleNegatedWithStars(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+  }
+
+  public void testMultipleNegatedWithStars(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    run(testBuilder.addKeepRules(
+            "-keep class !" + B.class.getTypeName() + ",!" + C.class.getTypeName() + ", ** { *; }"))
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), not(isPresent()));
+              assertThat(inspector.clazz(C.class), not(isPresent()));
+              assertThat(inspector.clazz(D.class), isPresent());
+              assertThat(inspector.clazz(FooBar.class), isPresent());
+              assertThat(inspector.clazz(BarBar.class), isPresent());
+            });
+  }
+
+  @Test
+  public void testFooBarR8Compat() throws Exception {
+    testFooBar(testForR8Compat(parameters.getBackend()));
+  }
+
+  @Test
+  public void testFooBarR8Full() throws Exception {
+    testFooBar(testForR8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testFooBarProguard() throws Exception {
+    testFooBar(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+  }
+
+  public void testFooBar(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    run(testBuilder.addKeepRules("-keep class !" + FooBar.class.getTypeName() + ", **Bar { *; }"))
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(BarBar.class), isPresent());
+              assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+              assertThat(inspector.clazz(A.class), not(isPresent()));
+              assertThat(inspector.clazz(B.class), not(isPresent()));
+              assertThat(inspector.clazz(C.class), not(isPresent()));
+              assertThat(inspector.clazz(D.class), not(isPresent()));
+            });
+  }
+
+  @Test
+  public void testMultipleNegatedR8Compat() throws Exception {
+    testMultipleNegated(testForR8Compat(parameters.getBackend()));
+  }
+
+  @Test
+  public void testMultipleNegatedR8Full() throws Exception {
+    testMultipleNegated(testForR8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testMultipleNegatedProguard() throws Exception {
+    testMultipleNegated(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+  }
+
+  public void testMultipleNegated(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    run(testBuilder.addKeepRules("-keep class !" + FooBar.class.getTypeName() + ", !**Bar { *; }"))
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(BarBar.class), not(isPresent()));
+              assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), isPresent());
+              assertThat(inspector.clazz(C.class), isPresent());
+              assertThat(inspector.clazz(D.class), isPresent());
+            });
+  }
+
+  private TestCompileResult<?, ?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    return testBuilder
+        .addProgramClasses(A.class, B.class, C.class, D.class, FooBar.class, BarBar.class)
+        .setMinApi(AndroidApiLevel.B)
+        .noMinification()
+        .compile();
+  }
+
+  public static class A {}
+
+  public static class B extends A {}
+
+  public static class C {}
+
+  public static class D extends A {}
+
+  public static class FooBar {}
+
+  public static class BarBar {}
+}
diff --git a/tools/retrace.py b/tools/retrace.py
index 70e3099..41b5a06 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -45,6 +45,11 @@
       default=None,
       action='store_true',
       help='Disables diagnostics printing to stdout.')
+  parser.add_argument(
+    '--debug-agent',
+    default=None,
+    action='store_true',
+    help='Attach a debug-agent to the retracer java process.')
   return parser.parse_args()
 
 
@@ -73,9 +78,11 @@
       args.stacktrace,
       args.commit_hash is not None,
       args.no_r8lib,
-      quiet=args.quiet)
+      quiet=args.quiet,
+      debug=args.debug_agent)
 
-def run(map_path, hash_or_version, stacktrace, is_hash, no_r8lib, quiet=False):
+def run(map_path, hash_or_version, stacktrace, is_hash, no_r8lib, quiet=False,
+        debug=False):
   if hash_or_version:
     download_path = archive.GetUploadDestination(
         hash_or_version,
@@ -88,8 +95,12 @@
       print('Could not find map file from argument: %s.' % hash_or_version)
       return 1
 
-  retrace_args = [
-    jdk.GetJavaExecutable(),
+  retrace_args = [jdk.GetJavaExecutable()]
+
+  if debug:
+    retrace_args.append('-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005')
+
+  retrace_args += [
     '-cp',
     utils.R8_JAR if no_r8lib else utils.R8LIB_JAR,
     'com.android.tools.r8.retrace.Retrace',