Merge commit '62e041b54dace122b1a70dfeb40d985951ca3d44' into dev-release
diff --git a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
index f77bbce..15acd6f 100644
--- a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
+++ b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
@@ -4,12 +4,14 @@
 
 package com.android.tools.r8;
 
-import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.ArrayList;
+import java.util.List;
 
 @Keep
 public class AssertionsConfiguration {
 
   /** The possible transformations of the javac generated assertion code during compilation. */
+  @Keep
   public enum AssertionTransformation {
     /** Unconditionally enable the javac generated assertion code. */
     ENABLE,
@@ -22,14 +24,55 @@
     PASSTHROUGH
   }
 
-  private AssertionTransformation transformation;
-
-  private AssertionsConfiguration(AssertionTransformation transformation) {
-    this.transformation = transformation;
+  public enum ConfigurationType {
+    ALL,
+    PACKAGE,
+    CLASS
   }
 
-  static Builder builder(DiagnosticsHandler handler) {
-    return new Builder(handler);
+  public static class ConfigurationEntry {
+    private final AssertionTransformation transformation;
+    private final ConfigurationType type;
+    private final String value;
+
+    private ConfigurationEntry(
+        AssertionTransformation transformation, ConfigurationType type, String value) {
+      assert value != null || type == ConfigurationType.ALL;
+      this.transformation = transformation;
+      this.type = type;
+      this.value = value;
+    }
+
+    public AssertionTransformation getTransformation() {
+      return transformation;
+    }
+
+    public ConfigurationType getType() {
+      return type;
+    }
+
+    public String getValue() {
+      return value;
+    }
+  }
+
+  // Methods which need to be public.
+  public static class InternalAssertionConfiguration {
+
+    public static List<ConfigurationEntry> getConfiguration(AssertionsConfiguration configuration) {
+      return configuration.entries;
+    }
+  }
+
+  private final List<ConfigurationEntry> entries;
+
+  private AssertionsConfiguration(List<ConfigurationEntry> entries) {
+    this.entries = entries;
+  }
+
+  static AssertionsConfiguration.Builder builder(AssertionsConfiguration previous) {
+    return new AssertionsConfiguration.Builder(
+        previous != null ? previous.entries : new ArrayList<>());
   }
 
   /**
@@ -38,24 +81,35 @@
    * <p>A builder is obtained by calling {@link
    * BaseCompilerCommand.Builder#addAssertionsConfiguration}.
    */
-  public AssertionTransformation getTransformation() {
-    return transformation;
-  }
-
   @Keep
   public static class Builder {
-    private AssertionTransformation transformation = null;
+    private final List<ConfigurationEntry> entries;
 
-    private final DiagnosticsHandler handler;
+    private Builder(List<ConfigurationEntry> previousEntries) {
+      assert previousEntries != null;
+      this.entries = previousEntries;
+    }
 
-    private Builder(DiagnosticsHandler handler) {
-      this.handler = handler;
+    private void addEntry(
+        AssertionTransformation transformation, ConfigurationType type, String value) {
+      entries.add(new ConfigurationEntry(transformation, type, value));
     }
 
     /** Set how to handle javac generated assertion code. */
     public AssertionsConfiguration.Builder setTransformation(
         AssertionTransformation transformation) {
-      this.transformation = transformation;
+      addEntry(transformation, ConfigurationType.ALL, null);
+      return this;
+    }
+
+    AssertionsConfiguration.Builder setDefault(AssertionTransformation transformation) {
+      // Add the default by inserting a transform all entry at the beginning of the list, if there
+      // isn't already one.
+      ConfigurationEntry defaultEntry =
+          new ConfigurationEntry(transformation, ConfigurationType.ALL, null);
+      if (entries.size() == 0 || entries.get(0).type != ConfigurationType.ALL) {
+        entries.listIterator().add(defaultEntry);
+      }
       return this;
     }
 
@@ -86,7 +140,7 @@
     /** Set how to handle javac generated assertion code in package and all subpackages. */
     public AssertionsConfiguration.Builder setTransformationForPackage(
         String packageName, AssertionTransformation transformation) {
-      handler.error(new StringDiagnostic("Unsupported"));
+      addEntry(transformation, ConfigurationType.PACKAGE, packageName);
       return this;
     }
 
@@ -123,7 +177,7 @@
     /** Set how to handle javac generated assertion code in class. */
     public AssertionsConfiguration.Builder setTransformationForClass(
         String className, AssertionTransformation transformation) {
-      handler.error(new StringDiagnostic("Unsupported"));
+      addEntry(transformation, ConfigurationType.CLASS, className);
       return this;
     }
 
@@ -151,7 +205,7 @@
 
     /** Build and return the {@link AssertionsConfiguration}. */
     public AssertionsConfiguration build() {
-      return new AssertionsConfiguration(transformation);
+      return new AssertionsConfiguration(entries);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index ce60661..2b94a54 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -134,8 +135,11 @@
     return optimizeMultidexForLinearAlloc;
   }
 
-  public AssertionsConfiguration getAssertionsConfiguration() {
-    return assertionsConfiguration;
+  AssertionsConfiguration getAssertionsConfiguration(
+      AssertionTransformation defaultTransformation) {
+    return AssertionsConfiguration.builder(assertionsConfiguration)
+        .setDefault(defaultTransformation)
+        .build();
   }
 
   Reporter getReporter() {
@@ -491,7 +495,8 @@
         Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
             assertionsConfigurationGenerator) {
       assertionsConfiguration =
-          assertionsConfigurationGenerator.apply(AssertionsConfiguration.builder(getReporter()));
+          assertionsConfigurationGenerator.apply(
+              AssertionsConfiguration.builder(assertionsConfiguration));
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2214e2d..d3ec378 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.D8Command.USAGE_MESSAGE;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
@@ -19,6 +18,7 @@
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
 import com.android.tools.r8.origin.CommandLineOrigin;
@@ -159,7 +159,7 @@
 
       final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
-      if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
+      if (!AssertionsRewriter.isPassthroughAll(options.assertionsConfiguration)) {
         // Run analysis to mark all <clinit> methods having the javac generated assertion
         // enabling code.
         ClassInitializerAssertionEnablingAnalysis analysis =
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 6bdc35c..2401aa5 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -361,15 +361,10 @@
     internal.desugaredLibraryConfiguration = libraryConfiguration;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
-    assert internal.assertionTransformation == null;
-    if (getAssertionsConfiguration() != null) {
-      internal.assertionTransformation = getAssertionsConfiguration().getTransformation();
-    }
-    if (internal.assertionTransformation == null) {
-      // Default, when no configuration is provided, is to disable all javac generated assertion
-      // code when generating dex.
-      internal.assertionTransformation = AssertionTransformation.DISABLE;
-    }
+    assert internal.assertionsConfiguration == null;
+    // Default, when no configuration is provided, is to remove all javac generated assertion
+    // code when generating dex.
+    internal.assertionsConfiguration = getAssertionsConfiguration(AssertionTransformation.DISABLE);
 
     return internal;
   }
diff --git a/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java b/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
index f771dd5..d75b2b0 100644
--- a/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
+++ b/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
@@ -10,17 +10,15 @@
 public interface DesugarGraphConsumer {
 
   /**
-   * Callback indicating that code originating from {@code src} was used to correctly desugar some
-   * code originating from {@code dst}.
-   *
-   * <p>In other words, {@code src} is a dependency for the desugaring of {@code dst}.
+   * Callback indicating that code originating from {@code dependency} is needed to correctly
+   * desugar code originating from {@code dependent}.
    *
    * <p>Note: this callback may be called on multiple threads.
    *
    * <p>Note: this callback places no guarantees on order of calls or on duplicate calls.
    *
-   * @param src Origin of some code input that is needed to desugar {@code dst}.
-   * @param dst Origin of some code that was dependent on code in {@code src}.
+   * @param dependent Origin of code that is dependent on code in {@code dependency}.
+   * @param dependency Origin of code that is a dependency to compile {@code dependent}.
    */
-  void accept(Origin src, Origin dst);
+  void accept(Origin dependent, Origin dependency);
 }
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index ff64d62..1346623 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -141,6 +142,11 @@
     // TODO(134732760): This is still work in progress.
     internal.desugaredLibraryConfiguration = libraryConfiguration;
 
+    assert internal.assertionsConfiguration == null;
+    // Default, when no configuration is provided, is to remove all javac generated assertion
+    // code when generating dex.
+    internal.assertionsConfiguration = getAssertionsConfiguration(AssertionTransformation.DISABLE);
+
     return internal;
   }
 
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index b906cb2..4a41c67 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -79,10 +79,16 @@
 
   class UseCollector extends UseRegistry {
 
+    private DexProgramClass context;
+
     UseCollector(DexItemFactory factory) {
       super(factory);
     }
 
+    public void setContext(DexProgramClass context) {
+      this.context = context;
+    }
+
     @Override
     public boolean registerInvokeVirtual(DexMethod method) {
       DexEncodedMethod target = appInfo.lookupVirtualTarget(method.holder, method);
@@ -231,7 +237,10 @@
     }
 
     private void registerMethod(DexEncodedMethod method) {
-      DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method.method, method.method.holder);
+      DexEncodedMethod superTarget =
+          appInfo
+              .resolveMethod(method.method.holder, method.method)
+              .lookupInvokeSpecialTarget(context, appInfo);
       if (superTarget != null) {
         addMethod(superTarget.method);
       }
@@ -344,6 +353,7 @@
   private void analyze() {
     UseCollector useCollector = new UseCollector(appInfo.dexItemFactory());
     for (DexProgramClass dexProgramClass : application.classes()) {
+      useCollector.setContext(dexProgramClass);
       useCollector.registerSuperType(dexProgramClass, dexProgramClass.superType);
       for (DexType implementsType : dexProgramClass.interfaces.values) {
         useCollector.registerSuperType(dexProgramClass, implementsType);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 03235a4..8c26650 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.R8Command.USAGE_MESSAGE;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
@@ -31,8 +30,8 @@
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.SourceDebugExtensionRewriter;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.ir.optimize.NestReducer;
@@ -321,12 +320,6 @@
 
         appView.rootSet().checkAllRulesAreUsed(options);
 
-        if (appView.options().enableSourceDebugExtensionRewriter) {
-          appView.setSourceDebugExtensionRewriter(
-              new SourceDebugExtensionRewriter(appView)
-                  .analyze(appView.withLiveness().appInfo()::isLiveProgramClass));
-        }
-
         if (options.proguardSeedsConsumer != null) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
@@ -733,7 +726,8 @@
       // When line number optimization is turned off the identity mapping for line numbers is
       // used. We still run the line number optimizer to collect line numbers and inline frame
       // information for the mapping file.
-      ClassNameMapper classNameMapper = LineNumberOptimizer.run(appView, application, namingLens);
+      ClassNameMapper classNameMapper =
+          LineNumberOptimizer.run(appView, application, inputApp, namingLens);
       timing.end();
       proguardMapSupplier = ProguardMapSupplier.fromClassNameMapper(classNameMapper, options);
 
@@ -813,7 +807,7 @@
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
       enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
     }
-    if (appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH) {
+    if (!AssertionsRewriter.isPassthroughAll(appView.options().assertionsConfiguration)) {
       enqueuer.registerAnalysis(
           new ClassInitializerAssertionEnablingAnalysis(
               appView.dexItemFactory(), OptimizationFeedbackSimple.getInstance()));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 45f9e84..265fa38 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -870,19 +870,14 @@
 
     internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
 
-    // Default is to remove Java assertion code as Dalvik and Art does not reliable support
-    // Java assertions. When generation class file output always keep the Java assertions code.
-    assert internal.assertionTransformation == null;
-    if (getAssertionsConfiguration() == null) {
-      // Default, when no configuration is provided, is to disable all javac generated assertion
-      // code when generating dex and leave it when generating class files.
-      internal.assertionTransformation =
-          internal.isGeneratingClassFiles()
-              ? AssertionTransformation.PASSTHROUGH
-              : AssertionTransformation.DISABLE;
-    } else {
-      internal.assertionTransformation = getAssertionsConfiguration().getTransformation();
-    }
+    assert internal.assertionsConfiguration == null;
+    // Default, when no configuration is provided, is to remove all javac generated assertion
+    // code when generating dex and leave it when generating class files.
+    internal.assertionsConfiguration =
+        getAssertionsConfiguration(
+            internal.isGeneratingClassFiles()
+                ? AssertionTransformation.PASSTHROUGH
+                : AssertionTransformation.DISABLE);
 
     // When generating class files the build is "intermediate" and we cannot pollute the namespace
     // with the a hard-coded outline class. Doing so would prohibit subsequent merging of two
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index cc8af52..86b2cb8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -5,9 +5,11 @@
 
 import com.android.tools.r8.graph.ResolutionResult.ArrayCloneMethodResult;
 import com.android.tools.r8.graph.ResolutionResult.ClassNotFoundResult;
+import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
 import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
 import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -143,6 +145,18 @@
     return app.definitionFor(type);
   }
 
+  public DexClass definitionForDesugarDependency(DexClass dependent, DexType type) {
+    if (dependent.type == type) {
+      return dependent;
+    }
+    DexClass definition = definitionFor(type);
+    if (definition != null && !definition.isLibraryClass() && dependent.isProgramClass()) {
+      InterfaceMethodRewriter.reportDependencyEdge(
+          dependent.asProgramClass(), definition, options());
+    }
+    return definition;
+  }
+
   @Override
   public DexProgramClass definitionForProgramType(DexType type) {
     return app.programDefinitionFor(type);
@@ -348,7 +362,7 @@
     assert checkIfObsolete();
     assert !clazz.isInterface();
     // Step 2:
-    SingleResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
+    ResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
     if (result != null) {
       return result;
     }
@@ -361,7 +375,7 @@
    * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
    * 5.4.3.3 of the JVM Spec</a>.
    */
-  private SingleResolutionResult resolveMethodOnClassStep2(
+  private ResolutionResult resolveMethodOnClassStep2(
       DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
     // Pt. 1: Signature polymorphic method check.
     // See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
@@ -373,6 +387,14 @@
     // Pt 2: Find a method that matches the descriptor.
     result = clazz.lookupMethod(method);
     if (result != null) {
+      // If the resolved method is private, then it can only be accessed if the symbolic reference
+      // that initiated the resolution was the type at which the method resolved on. If that is not
+      // the case, then the error is either an IllegalAccessError, or in the case where access is
+      // allowed because of nests, a NoSuchMethodError. Which error cannot be determined without
+      // knowing the calling context.
+      if (result.isPrivateMethod() && clazz != initialResolutionHolder) {
+        return new IllegalAccessOrNoSuchMethodResult(result);
+      }
       return new SingleResolutionResult(initialResolutionHolder, clazz, result);
     }
     // Pt 3: Apply step two to direct superclass of holder.
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 ac4e4eb..650bf61 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
 import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
@@ -13,7 +14,6 @@
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
 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.SourceDebugExtensionRewriter;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -22,6 +22,8 @@
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -58,7 +60,7 @@
   private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
   private VerticallyMergedClasses verticallyMergedClasses;
 
-  private SourceDebugExtensionRewriter sourceDebugExtensionRewriter;
+  private Map<DexClass, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
 
   private AppView(
       T appInfo, WholeProgramOptimizations wholeProgramOptimizations, InternalOptions options) {
@@ -144,14 +146,6 @@
     allCodeProcessed = true;
   }
 
-  public void setSourceDebugExtensionRewriter(SourceDebugExtensionRewriter rewriter) {
-    this.sourceDebugExtensionRewriter = rewriter;
-  }
-
-  public SourceDebugExtensionRewriter getSourceDebugExtensionRewriter() {
-    return this.sourceDebugExtensionRewriter;
-  }
-
   public AppServices appServices() {
     return appServices;
   }
@@ -169,6 +163,14 @@
     this.classesEscapingIntoLibrary = classesEscapingIntoLibrary;
   }
 
+  public void setSourceDebugExtensionForType(DexClass clazz, DexValueString sourceDebugExtension) {
+    this.sourceDebugExtensions.put(clazz, sourceDebugExtension);
+  }
+
+  public DexValueString getSourceDebugExtensionForType(DexClass clazz) {
+    return this.sourceDebugExtensions.get(clazz);
+  }
+
   @Override
   public final DexDefinition definitionFor(DexReference reference) {
     return appInfo().definitionFor(reference);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 2a87607..77545740 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -13,20 +13,24 @@
 import com.android.tools.r8.utils.PredicateUtils;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
 
 public abstract class DexClass extends DexDefinition {
@@ -210,14 +214,48 @@
     return Arrays.asList(virtualMethods);
   }
 
-  public List<DexEncodedMethod> kotlinFunctions(List<KmProperty> kmProperties) {
-    List<DexEncodedMethod> functions = new ArrayList<>();
-    for (DexEncodedMethod method : virtualMethods) {
-      if (method.isKotlinFunction(kmProperties)) {
-        functions.add(method);
+  public Map<DexEncodedMethod, KmConstructor> kotlinConstructors(
+      List<KmConstructor> constructors, AppView<?> appView) {
+    ImmutableMap.Builder<DexEncodedMethod, KmConstructor> builder = ImmutableMap.builder();
+    for (DexEncodedMethod method : directMethods) {
+      if (method.isInstanceInitializer()) {
+        KmConstructor constructor = method.findCompatibleKotlinConstructor(constructors, appView);
+        if (constructor != null) {
+          // Found a compatible constructor that is likely asked to keep.
+          builder.put(method, constructor);
+        }
       }
     }
-    return functions;
+    return builder.build();
+  }
+
+  public Map<DexEncodedMethod, KmFunction> kotlinExtensions(
+      List<KmFunction> extensions, AppView<?> appView) {
+    ImmutableMap.Builder<DexEncodedMethod, KmFunction> builder = ImmutableMap.builder();
+    for (DexEncodedMethod method : directMethods) {
+      KmFunction extension = method.findCompatibleKotlinExtension(extensions, appView);
+      if (extension != null) {
+        // Found a compatible extension that is likely asked to keep.
+        builder.put(method, extension);
+      }
+    }
+    return builder.build();
+  }
+
+  public List<DexEncodedMethod> kotlinFunctions(
+      List<KmFunction> functions, List<KmProperty> properties, AppView<?> appView) {
+    ImmutableList.Builder<DexEncodedMethod> builder = ImmutableList.builder();
+    for (DexEncodedMethod method : virtualMethods) {
+      KmFunction function = method.findCompatibleKotlinFunction(functions, appView);
+      if (function != null) {
+        // Found a compatible function that is likely asked to keep.
+        builder.add(method);
+      } else if (!method.isKotlinProperty(properties)) {
+        // This could be a newly merged method that is not part of properties.
+        builder.add(method);
+      }
+    }
+    return builder.build();
   }
 
   public void appendVirtualMethod(DexEncodedMethod method) {
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 e4fff90..dbd455b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -54,6 +54,7 @@
 import com.android.tools.r8.ir.synthetic.FieldAccessorSourceCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -73,6 +74,8 @@
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.IntPredicate;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
 import org.objectweb.asm.Opcodes;
 
@@ -349,8 +352,43 @@
     return accessFlags.isSynthetic();
   }
 
-  boolean isKotlinFunction(List<KmProperty> properties) {
-    return !isStaticMember() && !isKotlinProperty(properties);
+  // TODO(b/70169921): Handling JVM extensions as well.
+  KmConstructor findCompatibleKotlinConstructor(
+      List<KmConstructor> constructors, AppView<?> appView) {
+    if (!isInstanceInitializer()) {
+      return null;
+    }
+    for (KmConstructor constructor : constructors) {
+      if (KotlinMetadataSynthesizer.isCompatibleConstructor(constructor, this, appView)) {
+        return constructor;
+      }
+    }
+    return null;
+  }
+
+  // TODO(b/70169921): Handling JVM extensions as well.
+  KmFunction findCompatibleKotlinExtension(List<KmFunction> extensions, AppView<?> appView) {
+    if (!isStaticMember()) {
+      return null;
+    }
+    for (KmFunction extension : extensions) {
+      if (KotlinMetadataSynthesizer.isCompatibleExtension(extension, this, appView)) {
+        return extension;
+      }
+    }
+    return null;
+  }
+
+  KmFunction findCompatibleKotlinFunction(List<KmFunction> functions, AppView<?> appView) {
+    if (isStaticMember()) {
+      return null;
+    }
+    for (KmFunction function : functions) {
+      if (KotlinMetadataSynthesizer.isCompatibleFunction(function, this, appView)) {
+        return function;
+      }
+    }
+    return null;
   }
 
   // E.g., property `prop: T` is mapped to `getProp()T`, `setProp(T)V`, `prop$annotations()V`.
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 5d85611..ab96c0d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -119,6 +119,7 @@
   public final DexString longDescriptor = createString("J");
   public final DexString shortDescriptor = createString("S");
   public final DexString voidDescriptor = createString("V");
+  public final DexString descriptorSeparator = createString("/");
 
   public final DexString boxedBooleanDescriptor = createString("Ljava/lang/Boolean;");
   public final DexString boxedByteDescriptor = createString("Ljava/lang/Byte;");
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 131cee6..f3a829c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -442,6 +442,22 @@
     return true;
   }
 
+  public boolean contains(DexString s) {
+    // TODO(b/146621590): This does not handle character boundaries correctly.
+    int index = 0;
+    while (content.length - index >= s.content.length) {
+      int i = 0;
+      while (i < s.content.length - 1 && content[index + i] == s.content[i]) {
+        i++;
+      }
+      if (i == s.content.length - 1) {
+        return true;
+      }
+      index++;
+    }
+    return false;
+  }
+
   public boolean endsWith(DexString suffix) {
     if (content.length < suffix.content.length) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index cc39610..bbed9bc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -24,13 +24,19 @@
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
 
 public class DexType extends DexReference implements PresortedComparable<DexType> {
   public static final DexType[] EMPTY_ARRAY = {};
 
+  // Bundletool is merging classes that may originate from a build with an old version of R8.
+  // Allow merging of classes that use names from older versions of R8.
+  private static List<String> OLD_SYNTHESIZED_NAMES = ImmutableList.of("$r8$java8methods$utility");
+
   public final DexString descriptor;
   private String toStringCache = null;
 
@@ -264,7 +270,17 @@
         || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME)
         || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME)
         || name.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX)
-        || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME);
+        || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME)
+        || oldSynthesizedName(name);
+  }
+
+  private boolean oldSynthesizedName(String name) {
+    for (String synthesizedPrefix : OLD_SYNTHESIZED_NAMES) {
+      if (name.contains(synthesizedPrefix)) {
+        return true;
+      }
+    }
+    return false;
   }
 
   public boolean isProgramType(DexDefinitionSupplier definitions) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 147c165..27f4c9c 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -29,11 +29,11 @@
   }
 
   public Type getAsmObjectType(String name) {
-    return asmObjectTypeCache.computeIfAbsent(name, (key) -> Type.getObjectType(key));
+    return asmObjectTypeCache.computeIfAbsent(name, Type::getObjectType);
   }
 
   public Type getAsmType(String name) {
-    return asmTypeCache.computeIfAbsent(name, (key) -> Type.getType(key));
+    return asmTypeCache.computeIfAbsent(name, Type::getType);
   }
 
   public DexItemFactory getFactory() {
@@ -111,8 +111,8 @@
 
   public DexProto getProto(String desc) {
     assert isValidDescriptor(desc);
-    String returnTypeDescriptor = getReturnTypeDescriptor(desc);
-    String[] argumentDescriptors = getArgumentTypeDescriptors(desc);
+    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(desc);
+    String[] argumentDescriptors = DescriptorUtils.getArgumentTypeDescriptors(desc);
     StringBuilder shortyDescriptor = new StringBuilder();
     shortyDescriptor.append(getShortyDescriptor(returnTypeDescriptor));
     for (int i = 0; i < argumentDescriptors.length; i++) {
@@ -143,78 +143,6 @@
   }
 
   public Type getReturnType(final String methodDescriptor) {
-    return getAsmType(getReturnTypeDescriptor(methodDescriptor));
-  }
-
-  private static String getReturnTypeDescriptor(final String methodDescriptor) {
-    assert methodDescriptor.indexOf(')') != -1;
-    return methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
-  }
-
-  public static int getArgumentCount(final String methodDescriptor) {
-    int charIdx = 1;
-    char c;
-    int argCount = 0;
-    while ((c = methodDescriptor.charAt(charIdx++)) != ')') {
-      if (c == 'L') {
-        while (methodDescriptor.charAt(charIdx++) != ';');
-        argCount++;
-      } else if (c != '[') {
-        argCount++;
-      }
-    }
-    return argCount;
-  }
-
-  public Type[] getArgumentTypes(final String methodDescriptor) {
-    String[] argDescriptors = getArgumentTypeDescriptors(methodDescriptor);
-    Type[] args = new Type[argDescriptors.length];
-    int argIdx = 0;
-    for (String argDescriptor : argDescriptors) {
-      args[argIdx++] = getAsmType(argDescriptor);
-    }
-    return args;
-  }
-
-  private static String[] getArgumentTypeDescriptors(final String methodDescriptor) {
-    String[] argDescriptors = new String[getArgumentCount(methodDescriptor)];
-    int charIdx = 1;
-    char c;
-    int argIdx = 0;
-    int startType;
-    while ((c = methodDescriptor.charAt(charIdx)) != ')') {
-      switch (c) {
-        case 'V':
-          throw new Unreachable();
-        case 'Z':
-        case 'C':
-        case 'B':
-        case 'S':
-        case 'I':
-        case 'F':
-        case 'J':
-        case 'D':
-          argDescriptors[argIdx++] = Character.toString(c);
-          break;
-        case '[':
-          startType = charIdx;
-          while (methodDescriptor.charAt(++charIdx) == '[') {
-          }
-          if (methodDescriptor.charAt(charIdx) == 'L') {
-            while (methodDescriptor.charAt(++charIdx) != ';');
-          }
-          argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
-          break;
-        case 'L':
-          startType = charIdx;
-          while (methodDescriptor.charAt(++charIdx) != ';');
-          argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
-          break;
-        default:
-          throw new Unreachable();
-      }
-      charIdx++;
-    }
-    return argDescriptors;
+    return getAsmType(DescriptorUtils.getReturnTypeDescriptor(methodDescriptor));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index da8d8d7..cf4a98e 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -683,7 +683,7 @@
       this.parent = parent;
       this.method = parent.application.getMethod(parent.type, name, desc);
       this.flags = createMethodAccessFlags(name, access);
-      parameterCount = JarApplicationReader.getArgumentCount(desc);
+      parameterCount = DescriptorUtils.getArgumentCount(desc);
       if (exceptions != null && exceptions.length > 0) {
         DexValue[] values = new DexValue[exceptions.length];
         for (int i = 0; i < exceptions.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 65a2c7c..3080960 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -12,6 +12,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
+import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
 public abstract class ResolutionResult {
@@ -162,8 +163,70 @@
       if (!isAccessibleFrom(context, appInfo)) {
         return null;
       }
+      DexEncodedMethod target =
+          internalInvokeSpecialOrSuper(
+              context, appInfo, (sup, sub) -> isSuperclass(sup, sub, appInfo));
+      if (target == null) {
+        return null;
+      }
+      // Should we check access control again?
+      DexClass holder = appInfo.definitionFor(target.method.holder);
+      if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
+        return null;
+      }
+      return target;
+    }
 
-      // Statics cannot be targeted by invoke-special.
+    /**
+     * Lookup the target of an invoke-super.
+     *
+     * <p>This will return the target iff the resolution succeeded and the target is valid (i.e.,
+     * non-static and non-initializer) and accessible from {@code context}.
+     *
+     * <p>Additionally, this will also verify that the invoke-super is valid, i.e., it is on the a
+     * super type of the current context. Any invoke-special targeting the same type should have
+     * been mapped to an invoke-direct, but could change due to merging so we need to still allow
+     * the context to be equal to the targeted (symbolically referenced) type.
+     *
+     * @param context Class the invoke is contained in, i.e., the holder of the caller.
+     * @param appInfo Application info.
+     * @return The actual target for the invoke-super or {@code null} if no valid target is found.
+     */
+    @Override
+    public DexEncodedMethod lookupInvokeSuperTarget(
+        DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      if (!isAccessibleFrom(context, appInfo)) {
+        return null;
+      }
+      DexEncodedMethod target = lookupInvokeSuperTarget(context.asDexClass(), appInfo);
+      if (target == null) {
+        return null;
+      }
+      // Should we check access control again?
+      DexClass holder = appInfo.definitionFor(target.method.holder);
+      if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
+        return null;
+      }
+      return target;
+    }
+
+    @Override
+    public DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) {
+      assert context != null;
+      if (resolvedMethod.isInstanceInitializer()
+          || (appInfo.hasSubtyping()
+              && initialResolutionHolder != context
+              && !isSuperclass(initialResolutionHolder, context, appInfo.withSubtyping()))) {
+        throw new CompilationError(
+            "Illegal invoke-super to " + resolvedMethod.toSourceString(), context.getOrigin());
+      }
+      return internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
+    }
+
+    private DexEncodedMethod internalInvokeSpecialOrSuper(
+        DexClass context, AppInfo appInfo, BiPredicate<DexClass, DexClass> isSuperclass) {
+
+      // Statics cannot be targeted by invoke-special/super.
       if (getResolvedMethod().isStatic()) {
         return null;
       }
@@ -182,9 +245,9 @@
       final DexClass initialType;
       if (!resolvedMethod.isInstanceInitializer()
           && !symbolicReference.isInterface()
-          && isSuperclass(symbolicReference, context, appInfo)) {
+          && isSuperclass.test(symbolicReference, context)) {
         // If reference is a super type of the context then search starts at the immediate super.
-        initialType = appInfo.definitionFor(context.superType);
+        initialType = context.superType == null ? null : appInfo.definitionFor(context.superType);
       } else {
         // Otherwise it starts at the reference itself.
         initialType = symbolicReference;
@@ -228,10 +291,6 @@
       if (target.isAbstract()) {
         return null;
       }
-      // Should we check access control again?
-      if (!AccessControl.isMethodAccessible(target, initialType, context, appInfo)) {
-        return null;
-      }
       return target;
     }
 
@@ -239,72 +298,6 @@
       return sup != sub && appInfo.isSubtype(sub.type, sup.type);
     }
 
-    /**
-     * Lookup super method following the super chain from the holder of {@code method}.
-     *
-     * <p>This method will resolve the method on the holder of {@code method} and only return a
-     * non-null value if the result of resolution was an instance (i.e. non-static) method.
-     *
-     * <p>Additionally, this will also verify that the invoke super is valid, i.e., it is on the
-     * same type or a super type of the current context. The spec says that it has invoke super
-     * semantics, if the type is a supertype of the current class. If it is the same or a subtype,
-     * it has invoke direct semantics. The latter case is illegal, so we map it to a super call
-     * here. In R8, we abort at a later stage (see. See also <a href=
-     * "https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial" </a>
-     * for invokespecial dispatch and <a href="https://docs.oracle.com/javase/specs/jvms/"
-     * "se7/html/jvms-4.html#jvms-4.10.1.9.invokespecial"</a> for verification requirements. In
-     * particular, the requirement isAssignable(class(CurrentClassName, L), class(MethodClassName,
-     * L)). com.android.tools.r8.cf.code.CfInvoke#isInvokeSuper(DexType)}.
-     *
-     * @param context the class the invoke is contained in, i.e., the holder of the caller.
-     * @param appInfo Application info.
-     * @return The actual target for the invoke-super or {@code null} if none found.
-     */
-    @Override
-    public DexEncodedMethod lookupInvokeSuperTarget(
-        DexProgramClass context, AppInfoWithSubtyping appInfo) {
-      if (!isAccessibleFrom(context, appInfo)) {
-        return null;
-      }
-      return lookupInvokeSuperTarget(context.asDexClass(), appInfo);
-    }
-
-    @Override
-    public DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) {
-      assert context != null;
-      DexMethod method = resolvedMethod.method;
-      // TODO(b/145775365): Check the requirements for an invoke-special to a protected method.
-      // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial
-
-      if (appInfo.hasSubtyping()
-          && !appInfo.withSubtyping().isSubtype(context.type, initialResolutionHolder.type)) {
-        throw new CompilationError(
-            "Illegal invoke-super to " + method.toSourceString() + " from class " + context,
-            context.getOrigin());
-      }
-
-      // According to
-      // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial, use
-      // the "symbolic reference" if the "symbolic reference" does not name a class.
-      // TODO(b/145775365): This looks like the exact opposite of what the spec says, second item is
-      //  - is-class(sym-ref) => is-super(sym-ref, current-class)
-      //  this implication trivially holds for !is-class(sym-ref) == is-inteface(sym-ref), thus
-      //  the resolution should specifically *not* use the "symbolic reference".
-      if (initialResolutionHolder.isInterface()) {
-        // TODO(b/145775365): This does not consider a static method!
-        return appInfo.resolveMethodOnInterface(initialResolutionHolder, method).getSingleTarget();
-      }
-      // Then, resume on the search, but this time, starting from the holder of the caller.
-      if (context.superType == null) {
-        return null;
-      }
-      SingleResolutionResult resolution =
-          appInfo.resolveMethodOnClass(context.superType, method).asSingleResolution();
-      return resolution != null && !resolution.resolvedMethod.isStatic()
-          ? resolution.resolvedMethod
-          : null;
-    }
-
     @Override
     // TODO(b/140204899): Leverage refined receiver type if available.
     public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
@@ -515,33 +508,45 @@
     }
   }
 
-  public static class IncompatibleClassResult extends FailedResolutionResult {
-    static final IncompatibleClassResult INSTANCE =
-        new IncompatibleClassResult(Collections.emptyList());
+  abstract static class FailedResolutionWithCausingMethods extends FailedResolutionResult {
 
     private final Collection<DexEncodedMethod> methodsCausingError;
 
-    private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) {
+    private FailedResolutionWithCausingMethods(Collection<DexEncodedMethod> methodsCausingError) {
       this.methodsCausingError = methodsCausingError;
     }
 
-    static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) {
-      return methodsCausingError.isEmpty()
-          ? INSTANCE
-          : new IncompatibleClassResult(methodsCausingError);
-    }
-
     @Override
     public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
       this.methodsCausingError.forEach(methodCausingFailureConsumer);
     }
   }
 
-  public static class NoSuchMethodResult extends FailedResolutionResult {
-    static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
+  public static class IncompatibleClassResult extends FailedResolutionWithCausingMethods {
+    static final IncompatibleClassResult INSTANCE =
+        new IncompatibleClassResult(Collections.emptyList());
 
-    private NoSuchMethodResult() {
-      // Intentionally left empty.
+    private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) {
+      super(methodsCausingError);
+    }
+
+    static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) {
+      return methodsCausingError.isEmpty()
+          ? INSTANCE
+          : new IncompatibleClassResult(methodsCausingError);
+    }
+  }
+
+  public static class NoSuchMethodResult extends FailedResolutionResult {
+
+    static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
+  }
+
+  public static class IllegalAccessOrNoSuchMethodResult extends FailedResolutionWithCausingMethods {
+
+    public IllegalAccessOrNoSuchMethodResult(DexEncodedMethod methodCausingError) {
+      super(Collections.singletonList(methodCausingError));
+      assert methodCausingError != null;
     }
   }
 }
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 32b7cbe..00d2f79 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
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
@@ -49,6 +48,7 @@
 import com.android.tools.r8.ir.desugar.StringConcatRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.AliasIntroducer;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.Assumer;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -160,6 +160,7 @@
   public final Collection<Assumer> assumers = new ArrayList<>();
   private final DynamicTypeOptimization dynamicTypeOptimization;
 
+  final AssertionsRewriter assertionsRewriter;
   final DeadCodeRemover deadCodeRemover;
 
   private final MethodOptimizationInfoCollector methodOptimizationInfoCollector;
@@ -198,6 +199,7 @@
     this.stringOptimizer = new StringOptimizer(appView);
     this.stringBuilderOptimizer = new StringBuilderOptimizer(appView);
     this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
+    this.assertionsRewriter = new AssertionsRewriter(appView);
     this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView);
     this.neverMergePrefixes =
         options.neverMergePrefixes.stream()
@@ -766,7 +768,7 @@
     clearDexMethodCompilationState();
 
     if (identifierNameStringMarker != null) {
-      identifierNameStringMarker.decoupleIdentifierNameStringsInFields();
+      identifierNameStringMarker.decoupleIdentifierNameStringsInFields(executorService);
     }
 
     if (Log.ENABLED) {
@@ -1172,13 +1174,13 @@
     if (memberValuePropagation != null) {
       memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
     }
+
     if (options.enableEnumValueOptimization) {
       assert appView.enableWholeProgramOptimizations();
       codeRewriter.removeSwitchMaps(code);
     }
-    if (options.assertionTransformation != AssertionTransformation.PASSTHROUGH) {
-      codeRewriter.processAssertions(appView, method, code, feedback);
-    }
+
+    assertionsRewriter.run(method, code);
 
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java
deleted file mode 100644
index 20fc11c..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.conversion;
-
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfLabel;
-import com.android.tools.r8.cf.code.CfPosition;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
-import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.Multimaps;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Stack;
-import java.util.function.Predicate;
-
-public class SourceDebugExtensionRewriter {
-
-  private static final String SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX = "$i$f$";
-
-  private final AppView<?> appView;
-  private final DexItemFactory factory;
-
-  public SourceDebugExtensionRewriter(AppView<?> appView) {
-    this.appView = appView;
-    this.factory = appView.dexItemFactory();
-  }
-
-  public SourceDebugExtensionRewriter analyze(Predicate<DexProgramClass> shouldProcess) {
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (!shouldProcess.test(clazz)) {
-        continue;
-      }
-      DexAnnotation sourceDebug =
-          clazz.annotations.getFirstMatching(factory.annotationSourceDebugExtension);
-      if (sourceDebug == null || sourceDebug.annotation.elements.length != 1) {
-        continue;
-      }
-      DexValueString dexValueString = sourceDebug.annotation.elements[0].value.asDexValueString();
-      if (dexValueString == null) {
-        continue;
-      }
-      Result parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
-      if (parsedData == null) {
-        continue;
-      }
-      for (DexEncodedMethod method : clazz.methods()) {
-        if (method.getCode().isCfCode()) {
-          processMethod(method, parsedData);
-        }
-      }
-    }
-    return this;
-  }
-
-  private static class Context {
-
-    private Position currentPosition = null;
-    private LocalVariableInfo localVariableInliningInfoEntry = null;
-    private final Stack<CfLabel> endRangeLabels = new Stack<>();
-    private final List<CfInstruction> resultingList;
-    private final ImmutableListMultimap<CfLabel, LocalVariableInfo> localVariableInfoStartMap;
-    private int lastPosition = -1;
-
-    Context(
-        int initialSize,
-        ImmutableListMultimap<CfLabel, LocalVariableInfo> localVariableInfoStartMap) {
-      this.resultingList = new ArrayList<>(initialSize);
-      this.localVariableInfoStartMap = localVariableInfoStartMap;
-    }
-
-    String getInlinedFunctionName() {
-      return localVariableInliningInfoEntry
-          .getLocal()
-          .name
-          .toString()
-          .substring(SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX.length());
-    }
-  }
-
-  private void processMethod(DexEncodedMethod method, Result parsedData) {
-    CfCode cfCode = method.getCode().asCfCode();
-    Context context =
-        new Context(
-            cfCode.getInstructions().size() + parsedData.getPositions().size(),
-            Multimaps.index(cfCode.getLocalVariables(), LocalVariableInfo::getStart));
-    for (CfInstruction instruction : cfCode.getInstructions()) {
-      if (instruction.isLabel()) {
-        handleLabel(context, instruction.asLabel());
-      } else if (instruction.isPosition()
-          && (context.currentPosition != null || context.localVariableInliningInfoEntry != null)) {
-        handlePosition(context, instruction.asPosition(), parsedData);
-      } else {
-        context.resultingList.add(instruction);
-      }
-    }
-    cfCode.instructions = context.resultingList;
-  }
-
-  private void handleLabel(Context context, CfLabel label) {
-    ImmutableList<LocalVariableInfo> localVariableInfos =
-        context.localVariableInfoStartMap.get(label);
-    if (localVariableInfos != null) {
-      LocalVariableInfo newLocalVariableInliningInfo = null;
-      for (LocalVariableInfo localVariableInfo : localVariableInfos) {
-        String localVariableName = localVariableInfo.getLocal().name.toString();
-        if (!localVariableName.startsWith(SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX)) {
-          continue;
-        }
-        // Only one synthetic inlining label for a position should exist.
-        assert newLocalVariableInliningInfo == null;
-        newLocalVariableInliningInfo = localVariableInfo;
-      }
-      context.localVariableInliningInfoEntry = newLocalVariableInliningInfo;
-    }
-    while (!context.endRangeLabels.empty() && context.endRangeLabels.peek() == label) {
-      // The inlined range is ending here. Multiple inline ranges can end at the same label.
-      assert context.currentPosition != null;
-      context.currentPosition = context.currentPosition.callerPosition;
-      context.endRangeLabels.pop();
-    }
-    // Ensure endRangeLabels are in sync with the current position.
-    assert !context.endRangeLabels.empty() || context.currentPosition == null;
-    context.resultingList.add(label);
-  }
-
-  private void handlePosition(Context context, CfPosition position, Result parsedData) {
-    if (context.localVariableInliningInfoEntry != null) {
-      // This is potentially a new inlining frame.
-      KotlinSourceDebugExtensionParser.Position parsedInlinePosition =
-          parsedData.getPositions().get(position.getPosition().line);
-      if (parsedInlinePosition != null) {
-        String descriptor = "L" + parsedInlinePosition.getSource().getPath() + ";";
-        if (DescriptorUtils.isClassDescriptor(descriptor)) {
-          // This is a new inline function. Build up the inlining information from the parsed data
-          // and the local variable table.
-          DexType sourceHolder = factory.createType(descriptor);
-          final String inlinee = context.getInlinedFunctionName();
-          // TODO(b/145904809): See if we can find the inline function.
-          DexMethod syntheticExistingMethod =
-              factory.createMethod(
-                  sourceHolder,
-                  factory.createProto(factory.voidType),
-                  factory.createString(inlinee));
-          context.currentPosition =
-              new Position(
-                  parsedInlinePosition.getRange().from,
-                  null,
-                  syntheticExistingMethod,
-                  context.currentPosition);
-          context.endRangeLabels.push(context.localVariableInliningInfoEntry.getEnd());
-          context.lastPosition = position.getPosition().line;
-        }
-      }
-      context.localVariableInliningInfoEntry = null;
-    }
-    if (context.currentPosition != null) {
-      // We have a line-entry in a mapped range. Make sure to increment the index according to
-      // the delta in the inlined source.
-      Position currentPosition = context.currentPosition;
-      assert context.lastPosition > -1;
-      int delta = position.getPosition().line - context.lastPosition;
-      context.currentPosition =
-          new Position(
-              context.currentPosition.line + delta,
-              null,
-              currentPosition.method,
-              currentPosition.callerPosition);
-      // Append the original line index as the current caller context.
-      context.resultingList.add(
-          new CfPosition(
-              position.getLabel(),
-              appendAsOuterMostCaller(context.currentPosition, position.getPosition())));
-    } else {
-      context.resultingList.add(position);
-    }
-  }
-
-  private Position appendAsOuterMostCaller(Position position, Position callerPosition) {
-    if (position == null) {
-      return callerPosition;
-    } else {
-      return new Position(
-          position.line,
-          position.file,
-          position.method,
-          appendAsOuterMostCaller(position.callerPosition, callerPosition));
-    }
-  }
-}
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 fa68de4..8f33a5e 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
@@ -62,6 +62,8 @@
 
 public final class BackportedMethodRewriter {
 
+  // Don't change this name, at least not without adding special-casing in DexType to support
+  // merging old dex code in Bundletool.
   public static final String UTILITY_CLASS_NAME_PREFIX = "$r8$backportedMethods$utility";
 
   private final AppView<?> appView;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index f4fbdde..a4fc2e9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -140,12 +140,8 @@
               : closestProgramSubClass);
     }
 
-    public void reportDependency(DexClass clazz, AppView<?> appView) {
-      // If the direct subclass is in the compilation unit, report its dependencies.
-      if (clazz != directSubClass && directSubClass.isProgramClass()) {
-        InterfaceMethodRewriter.reportDependencyEdge(
-            clazz, directSubClass.asProgramClass(), appView);
-      }
+    public DexClass definitionFor(DexType type, AppView<?> appView) {
+      return appView.appInfo().definitionForDesugarDependency(directSubClass, type);
     }
 
     public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
@@ -168,8 +164,8 @@
     }
 
     @Override
-    public void reportDependency(DexClass clazz, AppView<?> appView) {
-      // Don't report dependencies in the library.
+    public DexClass definitionFor(DexType type, AppView<?> appView) {
+      return appView.definitionFor(type);
     }
 
     @Override
@@ -290,8 +286,9 @@
     ResolutionResult resolution = appView.appInfo().resolveMethod(clazz, method);
     // If resolution fails, install a method throwing IncompatibleClassChangeError.
     if (resolution.isFailedResolution()) {
-      assert resolution instanceof IncompatibleClassResult;
-      addICCEThrowingMethod(method, clazz);
+      if (resolution instanceof IncompatibleClassResult) {
+        addICCEThrowingMethod(method, clazz);
+      }
       return;
     }
     DexEncodedMethod target = resolution.getSingleTarget();
@@ -400,7 +397,7 @@
     if (type == null || type == dexItemFactory.objectType) {
       return null;
     }
-    DexClass clazz = appView.definitionFor(type);
+    DexClass clazz = context.definitionFor(type, appView);
     if (clazz == null) {
       context.reportMissingType(type, rewriter);
       return null;
@@ -418,7 +415,6 @@
     if (clazz.isLibraryClass()) {
       return ClassInfo.EMPTY;
     }
-    context.reportDependency(clazz, appView);
     return classInfo.computeIfAbsent(clazz, key -> visitClassInfoRaw(key, context));
   }
 
@@ -469,7 +465,6 @@
     if (iface.isLibraryClass() && ignoreLibraryInfo()) {
       return MethodSignatures.EMPTY;
     }
-    context.reportDependency(iface, appView);
     return interfaceInfo.computeIfAbsent(iface, key -> visitInterfaceInfoRaw(key, context));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index a43d321..7f4c043 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -961,7 +961,7 @@
     InterfaceProcessor processor = new InterfaceProcessor(appView, this);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, true)) {
-        processor.process(clazz.asProgramClass(), graphLensBuilder);
+        processor.process(clazz, graphLensBuilder);
       }
     }
     for (Entry<DexLibraryClass, Set<DexProgramClass>> entry : requiredDispatchClasses.entrySet()) {
@@ -1114,8 +1114,8 @@
 
     // At this point we likely have a non-library type that may depend on default method information
     // from its interfaces and the dependency should be reported.
-    if (!definedInterface.isLibraryClass()) {
-      reportDependencyEdge(definedInterface, implementing, appView);
+    if (implementing.isProgramClass() && !definedInterface.isLibraryClass()) {
+      reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.options());
     }
 
     // Merge information from all superinterfaces.
@@ -1139,13 +1139,14 @@
   }
 
   public static void reportDependencyEdge(
-      DexClass dependency, DexClass dependent, AppView<?> appView) {
-    DesugarGraphConsumer consumer = appView.options().desugarGraphConsumer;
+      DexProgramClass dependent, DexClass dependency, InternalOptions options) {
+    assert !dependency.isLibraryClass();
+    DesugarGraphConsumer consumer = options.desugarGraphConsumer;
     if (consumer != null) {
-      Origin dependencyOrigin = dependency.getOrigin();
       Origin dependentOrigin = dependent.getOrigin();
-      if (dependencyOrigin != dependentOrigin) {
-        consumer.accept(dependencyOrigin, dependentOrigin);
+      Origin dependencyOrigin = dependency.getOrigin();
+      if (dependentOrigin != dependencyOrigin) {
+        consumer.accept(dependentOrigin, dependencyOrigin);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 787fbe1..9599834 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.BiMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -302,6 +303,12 @@
     return true;
   }
 
+  private DexClass definitionForDependency(DexType dependency, DexClass dependent) {
+    return dependent.isProgramClass()
+        ? appView.appInfo().definitionForDesugarDependency(dependent.asProgramClass(), dependency)
+        : appView.definitionFor(dependency);
+  }
+
   // Returns true if the given interface method must be kept on [iface] after moving its
   // implementation to the companion class of [iface]. This is always the case for non-bridge
   // methods. Bridge methods that does not override an implementation in a super-interface must
@@ -313,32 +320,33 @@
       }
     }
     if (method.accessFlags.isBridge()) {
-      Deque<DexType> worklist = new ArrayDeque<>();
+      Deque<Pair<DexClass, DexType>> worklist = new ArrayDeque<>();
       Set<DexType> seenBefore = new HashSet<>();
-      if (iface.superType != null) {
-        worklist.add(iface.superType);
-      }
-      Collections.addAll(worklist, iface.interfaces.values);
+      addSuperTypes(iface, worklist);
       while (!worklist.isEmpty()) {
-        DexType superType = worklist.pop();
-        if (!seenBefore.add(superType)) {
+        Pair<DexClass, DexType> item = worklist.pop();
+        DexClass clazz = definitionForDependency(item.getSecond(), item.getFirst());
+        if (clazz == null || !seenBefore.add(clazz.type)) {
           continue;
         }
-        DexClass clazz = appView.definitionFor(superType);
-        if (clazz != null) {
-          if (clazz.lookupVirtualMethod(method.method) != null) {
-            return false;
-          }
-          if (clazz.superType != null) {
-            worklist.add(clazz.superType);
-          }
-          Collections.addAll(worklist, clazz.interfaces.values);
+        if (clazz.lookupVirtualMethod(method.method) != null) {
+          return false;
         }
+        addSuperTypes(clazz, worklist);
       }
     }
     return true;
   }
 
+  private static void addSuperTypes(DexClass clazz, Deque<Pair<DexClass, DexType>> worklist) {
+    if (clazz.superType != null) {
+      worklist.add(new Pair<>(clazz, clazz.superType));
+    }
+    for (DexType iface : clazz.interfaces.values) {
+      worklist.add(new Pair<>(clazz, iface));
+    }
+  }
+
   private boolean isStaticMethod(DexEncodedMethod method) {
     if (method.accessFlags.isNative()) {
       throw new Unimplemented("Native interface methods are not yet supported.");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
index f09f73f..5892466 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
@@ -15,7 +15,7 @@
   public static boolean isPinned(DexEncodedMethod method, AppView<AppInfoWithLiveness> appView) {
     return appView.appInfo().isPinned(method.method)
         || appView.appInfo().bootstrapMethods.contains(method.method)
-        || appView.appInfo().brokenSuperInvokes.contains(method.method)
+        || appView.appInfo().failedResolutionTargets.contains(method.method)
         || appView.appInfo().methodsTargetedByInvokeDynamic.contains(method.method)
         || method.accessFlags.isNative();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
new file mode 100644
index 0000000..2292b0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -0,0 +1,268 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.AssertionsConfiguration.ConfigurationEntry;
+import com.android.tools.r8.AssertionsConfiguration.ConfigurationType;
+import com.android.tools.r8.AssertionsConfiguration.InternalAssertionConfiguration;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ThrowingCharIterator;
+import java.io.UTFDataFormatException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class AssertionsRewriter {
+
+  private static class ConfigurationEntryWithDexString {
+
+    private ConfigurationEntry entry;
+    private final DexString value;
+
+    private ConfigurationEntryWithDexString(
+        ConfigurationEntry entry, DexItemFactory dexItemFactory) {
+      this.entry = entry;
+      switch (entry.getType()) {
+        case PACKAGE:
+          if (entry.getValue().length() == 0) {
+            value = dexItemFactory.createString("");
+          } else {
+            value =
+                dexItemFactory.createString(
+                    "L"
+                        + entry
+                            .getValue()
+                            .replace(
+                                DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+                                DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+                        + "/");
+          }
+          break;
+        case CLASS:
+          value =
+              dexItemFactory.createString(
+                  "L"
+                      + entry
+                          .getValue()
+                          .replace(
+                              DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+                              DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR)
+                      + ";");
+          break;
+        case ALL:
+          value = null;
+          break;
+        default:
+          throw new Unreachable();
+      }
+    }
+  }
+
+  private final AppView<?> appView;
+  private final DexItemFactory dexItemFactory;
+  private final List<ConfigurationEntryWithDexString> configuration;
+  private final boolean enabled;
+
+  public AssertionsRewriter(AppView<?> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+    if (appView.options().assertionsConfiguration == null) {
+      this.configuration = null;
+      this.enabled = false;
+    } else {
+      List<ConfigurationEntry> configuration =
+          InternalAssertionConfiguration
+              .getConfiguration(appView.options().assertionsConfiguration);
+      this.configuration =
+          configuration.stream()
+              .map(entry -> new ConfigurationEntryWithDexString(entry, appView.dexItemFactory()))
+              .collect(Collectors.toList());
+      this.enabled = !isPassthroughAll(appView.options().assertionsConfiguration);
+    }
+  }
+
+  public static boolean isPassthroughAll(AssertionsConfiguration assertionsConfiguration) {
+    List<ConfigurationEntry> configuration =
+        InternalAssertionConfiguration.getConfiguration(assertionsConfiguration);
+    return configuration.size() == 1
+        && configuration.get(0).getTransformation() == AssertionTransformation.PASSTHROUGH
+        && configuration.get(0).getType() == ConfigurationType.ALL;
+  }
+
+  private AssertionTransformation getTransformationForMethod(DexEncodedMethod method) {
+    AssertionTransformation transformation = null;
+    for (ConfigurationEntryWithDexString entry : configuration) {
+      switch (entry.entry.getType()) {
+        case ALL:
+          transformation = entry.entry.getTransformation();
+          break;
+        case PACKAGE:
+          if (entry.value.size == 0) {
+            if (!method.method.holder.descriptor.contains(dexItemFactory.descriptorSeparator)) {
+              transformation = entry.entry.getTransformation();
+            }
+          } else if (method.method.holder.descriptor.startsWith(entry.value)) {
+            transformation = entry.entry.getTransformation();
+          }
+          break;
+        case CLASS:
+          if (method.method.holder.descriptor.equals(entry.value)) {
+            transformation = entry.entry.getTransformation();
+          }
+          if (isDescriptorForClassOrInnerClass(entry.value, method.method.holder.descriptor)) {
+            transformation = entry.entry.getTransformation();
+          }
+          break;
+        default:
+          throw new Unreachable();
+      }
+    }
+    assert transformation != null; // Default transformation are always added.
+    return transformation;
+  }
+
+  private boolean isDescriptorForClassOrInnerClass(
+      DexString classDescriptor, DexString classOrInnerClassDescriptor) {
+    // Same string same class.
+    if (classOrInnerClassDescriptor == classDescriptor) {
+      return true;
+    }
+
+    // Check for inner class name by checking if the prefix is the class descriptor,
+    // where ';' is replaced whit '$' and no '/' after that.
+    if (classOrInnerClassDescriptor.size < classDescriptor.size) {
+      return false;
+    }
+    ThrowingCharIterator<UTFDataFormatException> i1 = classDescriptor.iterator();
+    ThrowingCharIterator<UTFDataFormatException> i2 = classOrInnerClassDescriptor.iterator();
+    try {
+      while (i1.hasNext()) {
+        char c1 = i1.nextChar();
+        char c2 = i2.nextChar();
+        // The Java VM behaviour is including all inner classes as well when a class is specified.
+        if (c1 == ';' && c2 == DescriptorUtils.INNER_CLASS_SEPARATOR) {
+          // If there is a '/' after the '$' this is not an inner class after all.
+          while (i2.hasNext()) {
+            if (i2.nextChar() == DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR) {
+              return false;
+            }
+          }
+          return true;
+        }
+        if (c1 != c2) {
+          return false;
+        }
+      }
+      assert i2.hasNext();
+      return false;
+    } catch (UTFDataFormatException e) {
+      return false;
+    }
+  }
+
+  /**
+   * For supporting assert javac adds the static field $assertionsDisabled to all classes which have
+   * methods with assertions. This is used to support the Java VM -ea flag.
+   *
+   * <p>The class:
+   *
+   * <pre>
+   * class A {
+   *   void m() {
+   *     assert xxx;
+   *   }
+   * }
+   * </pre>
+   *
+   * Is compiled into:
+   *
+   * <pre>
+   * class A {
+   *   static boolean $assertionsDisabled;
+   *   static {
+   *     $assertionsDisabled = A.class.desiredAssertionStatus();
+   *   }
+   *
+   *   // method with "assert xxx";
+   *   void m() {
+   *     if (!$assertionsDisabled) {
+   *       if (xxx) {
+   *         throw new AssertionError(...);
+   *       }
+   *     }
+   *   }
+   * }
+   * </pre>
+   *
+   * With the rewriting below (and other rewritings) the resulting code is:
+   *
+   * <pre>
+   * class A {
+   *   void m() {
+   *   }
+   * }
+   * </pre>
+   */
+  public void run(DexEncodedMethod method, IRCode code) {
+    if (!enabled) {
+      return;
+    }
+    AssertionTransformation transformation = getTransformationForMethod(method);
+    if (transformation == AssertionTransformation.PASSTHROUGH) {
+      return;
+    }
+    DexEncodedMethod clinit;
+    // If the <clinit> of this class did not have have code to turn on assertions don't try to
+    // remove assertion code from the method (including <clinit> itself.
+    if (method.isClassInitializer()) {
+      clinit = method;
+    } else {
+      DexClass clazz = appView.definitionFor(method.method.holder);
+      if (clazz == null) {
+        return;
+      }
+      clinit = clazz.getClassInitializer();
+    }
+    if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+      return;
+    }
+
+    // This code will process the assertion code in all methods including <clinit>.
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction current = iterator.next();
+      if (current.isInvokeMethod()) {
+        InvokeMethod invoke = current.asInvokeMethod();
+        if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
+          iterator.replaceCurrentInstruction(code.createIntConstant(0));
+        }
+      } else if (current.isStaticPut()) {
+        StaticPut staticPut = current.asStaticPut();
+        if (staticPut.getField().name == dexItemFactory.assertionsDisabled) {
+          iterator.remove();
+        }
+      } else if (current.isStaticGet()) {
+        StaticGet staticGet = current.asStaticGet();
+        if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
+          iterator.replaceCurrentInstruction(
+              code.createIntConstant(transformation == AssertionTransformation.DISABLE ? 1 : 0));
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 527874a..65e86b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -9,7 +9,6 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
@@ -72,7 +71,6 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
@@ -80,7 +78,6 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1275,95 +1272,6 @@
     assert code.isConsistentSSA();
   }
 
-  /**
-   * For supporting assert javac adds the static field $assertionsDisabled to all classes which have
-   * methods with assertions. This is used to support the Java VM -ea flag.
-   *
-   * <p>The class:
-   *
-   * <pre>
-   * class A {
-   *   void m() {
-   *     assert xxx;
-   *   }
-   * }
-   * </pre>
-   *
-   * Is compiled into:
-   *
-   * <pre>
-   * class A {
-   *   static boolean $assertionsDisabled;
-   *   static {
-   *     $assertionsDisabled = A.class.desiredAssertionStatus();
-   *   }
-   *
-   *   // method with "assert xxx";
-   *   void m() {
-   *     if (!$assertionsDisabled) {
-   *       if (xxx) {
-   *         throw new AssertionError(...);
-   *       }
-   *     }
-   *   }
-   * }
-   * </pre>
-   *
-   * With the rewriting below (and other rewritings) the resulting code is:
-   *
-   * <pre>
-   * class A {
-   *   void m() {
-   *   }
-   * }
-   * </pre>
-   */
-  public void processAssertions(
-      AppView<?> appView, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    assert appView.options().assertionTransformation != AssertionTransformation.PASSTHROUGH;
-    DexEncodedMethod clinit;
-    // If the <clinit> of this class did not have have code to turn on assertions don't try to
-    // remove assertion code from the method (including <clinit> itself.
-    if (method.isClassInitializer()) {
-      clinit = method;
-    } else {
-      DexClass clazz = appView.definitionFor(method.method.holder);
-      if (clazz == null) {
-        return;
-      }
-      clinit = clazz.getClassInitializer();
-    }
-    if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
-      return;
-    }
-
-    // This code will process the assertion code in all methods including <clinit>.
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Instruction current = iterator.next();
-      if (current.isInvokeMethod()) {
-        InvokeMethod invoke = current.asInvokeMethod();
-        if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
-          iterator.replaceCurrentInstruction(code.createIntConstant(0));
-        }
-      } else if (current.isStaticPut()) {
-        StaticPut staticPut = current.asStaticPut();
-        if (staticPut.getField().name == dexItemFactory.assertionsDisabled) {
-          iterator.remove();
-        }
-      } else if (current.isStaticGet()) {
-        StaticGet staticGet = current.asStaticGet();
-        if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
-          iterator.replaceCurrentInstruction(
-              code.createIntConstant(
-                  appView.options().assertionTransformation == AssertionTransformation.DISABLE
-                      ? 1
-                      : 0));
-        }
-      }
-    }
-  }
-
   enum RemoveCheckCastInstructionIfTrivialResult {
     NO_REMOVALS,
     REMOVED_CAST_DO_NARROW
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index a938ff2..194b84a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -227,9 +227,8 @@
     for (int i = 0; i < directMethods.size(); i++) {
       DexEncodedMethod method = directMethods.get(i);
 
-      // If this is a private or static method that is targeted by an invoke-super instruction, then
-      // don't remove any unused arguments.
-      if (appView.appInfo().brokenSuperInvokes.contains(method.method)) {
+      // If this is a method with known resolution issues, then don't remove any unused arguments.
+      if (appView.appInfo().failedResolutionTargets.contains(method.method)) {
         continue;
       }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index e76bbcf..06db71b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -5,8 +5,11 @@
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.isExtension;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmConstructor;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunctionAsExtension;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
 
 import com.android.tools.r8.graph.AppView;
@@ -15,8 +18,12 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
@@ -65,13 +72,42 @@
       superTypes.add(toKmType(addKotlinPrefix("Any;")));
     }
 
+    List<KmConstructor> constructors = kmClass.getConstructors();
+    List<KmConstructor> originalConstructors = new ArrayList<>(constructors);
+    constructors.clear();
+    for (Map.Entry<DexEncodedMethod, KmConstructor> entry :
+        clazz.kotlinConstructors(originalConstructors, appView).entrySet()) {
+      KmConstructor constructor =
+          toRenamedKmConstructor(entry.getKey(), entry.getValue(), appView, lens);
+      if (constructor != null) {
+        constructors.add(constructor);
+      }
+    }
+
     List<KmFunction> functions = kmClass.getFunctions();
+    List<KmFunction> originalFunctions =
+        functions.stream()
+            .filter(kmFunction -> !isExtension(kmFunction))
+            .collect(Collectors.toList());
+    List<KmFunction> originalExtensions =
+        functions.stream()
+            .filter(KotlinMetadataSynthesizer::isExtension)
+            .collect(Collectors.toList());
     functions.clear();
+
     List<KmProperty> properties = kmClass.getProperties();
-    for (DexEncodedMethod method : clazz.kotlinFunctions(properties)) {
-      KmFunction kmFunction = toRenamedKmFunction(method.method, appView, lens);
-      if (kmFunction != null) {
-        functions.add(kmFunction);
+    for (DexEncodedMethod method : clazz.kotlinFunctions(originalFunctions, properties, appView)) {
+      KmFunction function = toRenamedKmFunction(method, null, appView, lens);
+      if (function != null) {
+        functions.add(function);
+      }
+    }
+    for (Map.Entry<DexEncodedMethod, KmFunction> entry :
+        clazz.kotlinExtensions(originalExtensions, appView).entrySet()) {
+      KmFunction extension =
+          toRenamedKmFunctionAsExtension(entry.getKey(), entry.getValue(), appView, lens);
+      if (extension != null) {
+        functions.add(extension);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 2b1f9bc..68b607df 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,10 +4,17 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunctionAsExtension;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -37,15 +44,29 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // TODO(b/70169921): no idea yet!
-    assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
-        : toString();
+    List<KmFunction> functions = kmPackage.getFunctions();
+    List<KmFunction> originalExtensions =
+        functions.stream()
+            .filter(KotlinMetadataSynthesizer::isExtension)
+            .collect(Collectors.toList());
+    functions.clear();
+
+    for (Map.Entry<DexEncodedMethod, KmFunction> entry :
+        clazz.kotlinExtensions(originalExtensions, appView).entrySet()) {
+      KmFunction extension =
+          toRenamedKmFunctionAsExtension(entry.getKey(), entry.getValue(), appView, lens);
+      if (extension != null) {
+        functions.add(extension);
+      }
+    }
   }
 
   @Override
   KotlinClassHeader createHeader() {
-    // TODO(b/70169921): may need to update if `rewrite` is implemented.
-    return metadata.getHeader();
+    KotlinClassMetadata.MultiFileClassPart.Writer writer =
+        new KotlinClassMetadata.MultiFileClassPart.Writer();
+    kmPackage.accept(writer);
+    return writer.write(metadata.getFacadeClassName()).getHeader();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index e2bb332..e0a3935 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -4,10 +4,17 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunctionAsExtension;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -37,15 +44,28 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // TODO(b/70169921): no idea yet!
-    assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
-        : toString();
+    List<KmFunction> functions = kmPackage.getFunctions();
+    List<KmFunction> originalExtensions =
+        functions.stream()
+            .filter(KotlinMetadataSynthesizer::isExtension)
+            .collect(Collectors.toList());
+    functions.clear();
+
+    for (Map.Entry<DexEncodedMethod, KmFunction> entry :
+        clazz.kotlinExtensions(originalExtensions, appView).entrySet()) {
+      KmFunction extension =
+          toRenamedKmFunctionAsExtension(entry.getKey(), entry.getValue(), appView, lens);
+      if (extension != null) {
+        functions.add(extension);
+      }
+    }
   }
 
   @Override
   KotlinClassHeader createHeader() {
-    // TODO(b/70169921): may need to update if `rewrite` is implemented.
-    return metadata.getHeader();
+    KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
+    kmPackage.accept(writer);
+    return writer.write().getHeader();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 0e195e7..ff9ad15 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToInternalName;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
 import static kotlinx.metadata.FlagsKt.flagsOf;
 
 import com.android.tools.r8.graph.AppView;
@@ -15,12 +16,19 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
 import java.util.List;
+import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmValueParameter;
 
-class KotlinMetadataSynthesizer {
+public class KotlinMetadataSynthesizer {
+
+  static boolean isExtension(KmFunction kmFunction) {
+    return kmFunction.getReceiverParameterType() != null;
+  }
+
   static KmType toKmType(String descriptor) {
     KmType kmType = new KmType(flagsOf());
     kmType.visitClass(descriptorToInternalName(descriptor));
@@ -61,34 +69,176 @@
     return kmType;
   }
 
-  static KmFunction toRenamedKmFunction(
-      DexMethod method, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    DexEncodedMethod encodedMethod = appView.definitionFor(method);
-    if (encodedMethod == null) {
+  private static boolean isCompatible(KmType kmType, DexType type, AppView<?> appView) {
+    if (kmType == null || type == null) {
+      return false;
+    }
+    String descriptor = null;
+    if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
+      DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
+      descriptor = convertedType.toDescriptorString();
+    }
+    if (descriptor == null) {
+      descriptor = type.toDescriptorString();
+    }
+    assert descriptor != null;
+    return descriptor.equals(getDescriptorFromKmType(kmType));
+  }
+
+  public static boolean isCompatibleConstructor(
+      KmConstructor constructor, DexEncodedMethod method, AppView<?> appView) {
+    List<KmValueParameter> parameters = constructor.getValueParameters();
+    if (method.method.proto.parameters.size() != parameters.size()) {
+      return false;
+    }
+    for (int i = 0; i < method.method.proto.parameters.size(); i++) {
+      KmType kmType = parameters.get(i).getType();
+      if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public static boolean isCompatibleFunction(
+      KmFunction function, DexEncodedMethod method, AppView<?> appView) {
+    if (!function.getName().equals(method.method.name.toString())) {
+      return false;
+    }
+    if (!isCompatible(function.getReturnType(), method.method.proto.returnType, appView)) {
+      return false;
+    }
+    List<KmValueParameter> parameters = function.getValueParameters();
+    if (method.method.proto.parameters.size() != parameters.size()) {
+      return false;
+    }
+    for (int i = 0; i < method.method.proto.parameters.size(); i++) {
+      KmType kmType = parameters.get(i).getType();
+      if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // TODO(b/70169921): Handling JVM extensions as well.
+  public static boolean isCompatibleExtension(
+      KmFunction extension, DexEncodedMethod method, AppView<?> appView) {
+    if (!extension.getName().equals(method.method.name.toString())) {
+      return false;
+    }
+    if (!isCompatible(extension.getReturnType(), method.method.proto.returnType, appView)) {
+      return false;
+    }
+    List<KmValueParameter> parameters = extension.getValueParameters();
+    if (method.method.proto.parameters.size() != parameters.size() + 1) {
+      return false;
+    }
+    assert method.method.proto.parameters.size() > 0;
+    assert extension.getReceiverParameterType() != null;
+    if (!isCompatible(
+        extension.getReceiverParameterType(), method.method.proto.parameters.values[0], appView)) {
+      return false;
+    }
+    for (int i = 1; i < method.method.proto.parameters.size(); i++) {
+      KmType kmType = parameters.get(i - 1).getType();
+      if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  static KmConstructor toRenamedKmConstructor(
+      DexEncodedMethod method,
+      KmConstructor original,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens lens) {
+    // Make sure it is an instance initializer and live.
+    if (!method.isInstanceInitializer()
+        || !appView.appInfo().liveMethods.contains(method.method)) {
       return null;
     }
+    // TODO(b/70169921): {@link KmConstructor.extensions} is private, i.e., no way to alter!
+    //   Thus, we rely on original metadata for now.
+    Box<Boolean> hasJvmExtension = new Box<>(false);
+    KmConstructor kmConstructor =
+        hasJvmExtension.get()
+            ? original
+            // TODO(b/70169921): Consult kotlinx.metadata.Flag.Constructor to set IS_PRIMARY.
+            : new KmConstructor(method.accessFlags.getAsKotlinFlags());
+    List<KmValueParameter> parameters = kmConstructor.getValueParameters();
+    parameters.clear();
+    populateKmValueParameters(parameters, method, appView, lens, false);
+    return kmConstructor;
+  }
+
+  static KmFunction toRenamedKmFunction(
+      DexEncodedMethod method,
+      KmFunction original,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens lens) {
+    return toRenamedKmFunctionHelper(method, original, appView, lens, false);
+  }
+
+  static KmFunction toRenamedKmFunctionAsExtension(
+      DexEncodedMethod method,
+      KmFunction original,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens lens) {
+    return toRenamedKmFunctionHelper(method, original, appView, lens, true);
+  }
+
+  private static KmFunction toRenamedKmFunctionHelper(
+      DexEncodedMethod method,
+      KmFunction original,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens lens,
+      boolean isExtension) {
     // For library overrides, synthesize @Metadata always.
     // For regular methods, make sure it is live.
-    if (!encodedMethod.isLibraryMethodOverride().isTrue()
-        && !appView.appInfo().liveMethods.contains(method)) {
+    if (!method.isLibraryMethodOverride().isTrue()
+        && !appView.appInfo().liveMethods.contains(method.method)) {
       return null;
     }
-    DexMethod renamedMethod = lens.lookupMethod(method, appView.dexItemFactory());
+    DexMethod renamedMethod = lens.lookupMethod(method.method, appView.dexItemFactory());
     // For a library method override, we should not have renamed it.
-    assert !encodedMethod.isLibraryMethodOverride().isTrue() || renamedMethod == method
+    assert !method.isLibraryMethodOverride().isTrue() || renamedMethod == method.method
         : method.toSourceString() + " -> " + renamedMethod.toSourceString();
-    // TODO(b/70169921): Consult kotlinx.metadata.Flag.Function for kind (e.g., suspend).
+    // TODO(b/70169921): {@link KmFunction.extensions} is private, i.e., no way to alter!
+    //   Thus, we rely on original metadata for now.
+    assert !isExtension || original != null;
     KmFunction kmFunction =
-        new KmFunction(encodedMethod.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
-    KmType kmReturnType = toRenamedKmType(method.proto.returnType, appView, lens);
+        isExtension
+            ? original
+            // TODO(b/70169921): Consult kotlinx.metadata.Flag.Function for kind (e.g., suspend).
+            : new KmFunction(method.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
+    KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
     assert kmReturnType != null;
     kmFunction.setReturnType(kmReturnType);
+    if (isExtension) {
+      assert method.method.proto.parameters.values.length > 0;
+      KmType kmReceiverType =
+          toRenamedKmType(method.method.proto.parameters.values[0], appView, lens);
+      assert kmReceiverType != null;
+      kmFunction.setReceiverParameterType(kmReceiverType);
+    }
     List<KmValueParameter> parameters = kmFunction.getValueParameters();
-    for (int i = 0; i < method.proto.parameters.values.length; i++) {
-      DexType paramType = method.proto.parameters.values[i];
-      DebugLocalInfo debugLocalInfo = encodedMethod.getParameterInfo().get(i);
-      String parameterName =
-          debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
+    parameters.clear();
+    populateKmValueParameters(parameters, method, appView, lens, isExtension);
+    return kmFunction;
+  }
+
+  private static void populateKmValueParameters(
+      List<KmValueParameter> parameters,
+      DexEncodedMethod method,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens lens,
+      boolean isExtension) {
+    for (int i = isExtension ? 1 : 0; i < method.method.proto.parameters.values.length; i++) {
+      DexType paramType = method.method.proto.parameters.values[i];
+      DebugLocalInfo debugLocalInfo = method.getParameterInfo().get(i);
+      String parameterName = debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
       // TODO(b/70169921): Consult kotlinx.metadata.Flag.ValueParameter.
       KmValueParameter kmValueParameter = new KmValueParameter(flagsOf(), parameterName);
       KmType kmParamType = toRenamedKmType(paramType, appView, lens);
@@ -96,6 +246,5 @@
       kmValueParameter.setType(kmParamType);
       parameters.add(kmValueParameter);
     }
-    return kmFunction;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
index a0d4df2..8c6c2b8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.kotlin;
 
 import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.utils.SegmentTree;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import java.io.BufferedReader;
 import java.io.Closeable;
@@ -211,9 +212,10 @@
 
   private static void addDebugEntryToBuilder(String debugEntry, ResultBuilder builder)
       throws KotlinSourceDebugExtensionParserException {
-    // <from>#<file>,<to>:<debug-line-position>
+    // <from>#<file>,<size>:<debug-line-position>
     // or
     // <from>#<file>:<debug-line-position>
+    // All positions should define intervals for mappings.
     try {
       int targetSplit = debugEntry.indexOf(':');
       int target = Integer.parseInt(debugEntry.substring(targetSplit + 1));
@@ -223,7 +225,7 @@
       // The range may have a different end than start.
       String fileAndEndRange = original.substring(fileIndexSplit + 1);
       int endRangeCharPosition = fileAndEndRange.indexOf(',');
-      int size = originalStart;
+      int size = 1;
       if (endRangeCharPosition > -1) {
         // The file should be at least one number wide.
         assert endRangeCharPosition > 0;
@@ -233,16 +235,13 @@
       }
       int fileIndex = Integer.parseInt(fileAndEndRange.substring(0, endRangeCharPosition));
       Source thisFileSource = builder.files.get(fileIndex);
-      if (thisFileSource != null) {
-        Range range = new Range(originalStart, originalStart + size);
-        Position position = new Position(thisFileSource, range);
-        Position existingPosition = builder.positions.put(target, position);
-        assert existingPosition == null
-            : "Position index "
-                + target
-                + " was already mapped to an existing position: "
-                + position;
+      if (thisFileSource == null) {
+        throw new KotlinSourceDebugExtensionParserException(
+            "Could not find file with index " + fileIndex);
       }
+      Range range = new Range(originalStart, originalStart + (size - 1));
+      Position position = new Position(thisFileSource, range);
+      builder.segmentTree.add(target, target + (size - 1), position);
     } catch (NumberFormatException e) {
       throw new KotlinSourceDebugExtensionParserException("Could not convert position to number");
     }
@@ -250,29 +249,28 @@
 
   public static class Result {
 
-    private final Map<Integer, Source> files;
-    private final Map<Integer, Position> positions;
+    private final SegmentTree<Position> segmentTree;
 
-    private Result(Map<Integer, Source> files, Map<Integer, Position> positions) {
-      this.files = files;
-      this.positions = positions;
+    public Result(SegmentTree<Position> segmentTree) {
+      this.segmentTree = segmentTree;
     }
 
-    public Map<Integer, Source> getFiles() {
-      return files;
+    public Map.Entry<Integer, Position> lookup(int point) {
+      return segmentTree.findEntry(point);
     }
 
-    public Map<Integer, Position> getPositions() {
-      return positions;
+    public int size() {
+      return segmentTree.size();
     }
   }
 
   public static class ResultBuilder {
-    final Map<Integer, Source> files = new HashMap<>();
-    final Map<Integer, Position> positions = new HashMap<>();
+
+    SegmentTree<Position> segmentTree = new SegmentTree<>(false);
+    Map<Integer, Source> files = new HashMap<>();
 
     public Result build() {
-      return new Result(files, positions);
+      return new Result(segmentTree);
     }
   }
 
@@ -326,6 +324,7 @@
         sb.append(",");
         sb.append(range.to);
       }
+      sb.append(":");
       return sb.toString();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 75d1037..de60241 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -40,6 +39,7 @@
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Streams;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.util.Arrays;
@@ -47,6 +47,8 @@
 import java.util.ListIterator;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 
 public class IdentifierNameStringMarker {
@@ -61,12 +63,17 @@
     this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
   }
 
-  public void decoupleIdentifierNameStringsInFields() {
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      for (DexEncodedField field : clazz.staticFields()) {
-        decoupleIdentifierNameStringInStaticField(field);
-      }
-    }
+  public void decoupleIdentifierNameStringsInFields(
+      ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(
+        appView.appInfo().classes(),
+        clazz -> {
+          for (DexEncodedField field : clazz.staticFields()) {
+            decoupleIdentifierNameStringInStaticField(field);
+          }
+        },
+        executorService
+    );
   }
 
   private void decoupleIdentifierNameStringInStaticField(DexEncodedField encodedField) {
diff --git a/src/main/java/com/android/tools/r8/retrace/AmbiguousComparator.java b/src/main/java/com/android/tools/r8/retrace/AmbiguousComparator.java
new file mode 100644
index 0000000..35266e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/AmbiguousComparator.java
@@ -0,0 +1,47 @@
+// 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.retrace;
+
+import java.util.Comparator;
+import java.util.function.BiFunction;
+
+public abstract class AmbiguousComparator<T> implements Comparator<T> {
+
+  public enum SortKeys {
+    CLASS,
+    METHOD,
+    SOURCE,
+    LINE
+  }
+
+  private final BiFunction<T, SortKeys, String> getter;
+
+  public AmbiguousComparator(BiFunction<T, SortKeys, String> getter) {
+    this.getter = getter;
+  }
+
+  @Override
+  public int compare(T o1, T o2) {
+    int compare = getter.apply(o1, SortKeys.CLASS).compareTo(getter.apply(o2, SortKeys.CLASS));
+    if (compare != 0) {
+      return compare;
+    }
+    compare = getter.apply(o1, SortKeys.METHOD).compareTo(getter.apply(o2, SortKeys.METHOD));
+    if (compare != 0) {
+      return compare;
+    }
+    compare = getter.apply(o1, SortKeys.SOURCE).compareTo(getter.apply(o2, SortKeys.SOURCE));
+    if (compare != 0) {
+      return compare;
+    }
+    try {
+      return Integer.compare(
+          Integer.parseInt(getter.apply(o1, SortKeys.LINE)),
+          Integer.parseInt(getter.apply(o2, SortKeys.SOURCE)));
+    } catch (NumberFormatException ignore) {
+      return 0;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index d537f03..f0719ea 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -162,8 +162,20 @@
       return mappedRange != null ? mappedRange.getOriginalLineNumber(linePosition) : linePosition;
     }
 
+    public boolean containsMinifiedLineNumber(int linePosition) {
+      if (hasNoLineNumberRange()) {
+        return false;
+      }
+      return mappedRange.minifiedRange.from <= linePosition
+          && linePosition <= mappedRange.minifiedRange.to;
+    }
+
+    public boolean hasNoLineNumberRange() {
+      return mappedRange == null || mappedRange.minifiedRange == null;
+    }
+
     public int getFirstLineNumberOfOriginalRange() {
-      if (mappedRange == null) {
+      if (hasNoLineNumberRange()) {
         return 0;
       }
       return mappedRange.getFirstLineNumberOfOriginalRange();
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
index d6b4cb9..b561305 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.retrace.RetraceRegularExpression.RetraceString.RetraceStringBuilder;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -75,14 +76,56 @@
         // We could not find a match. Output the identity.
         result.add(string);
       } else {
+        boolean isAmbiguous = retracedStrings.size() > 1 && retracedStrings.get(0).isAmbiguous;
+        if (isAmbiguous) {
+          retracedStrings.sort(new RetraceLineComparator());
+        }
+        ClassReference previousContext = null;
         for (RetraceString retracedString : retracedStrings) {
-          result.add(retracedString.getRetracedString());
+          String finalString = retracedString.getRetracedString();
+          if (!isAmbiguous) {
+            result.add(finalString);
+            continue;
+          }
+          assert retracedString.getClassContext() != null;
+          ClassReference currentContext = retracedString.getClassContext().getClassReference();
+          if (currentContext.equals(previousContext)) {
+            int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(finalString);
+            finalString =
+                finalString.substring(0, firstNonWhitespaceCharacter)
+                    + "<OR> "
+                    + finalString.substring(firstNonWhitespaceCharacter);
+          }
+          previousContext = currentContext;
+          result.add(finalString);
         }
       }
     }
     return new RetraceCommandLineResult(result);
   }
 
+  static class RetraceLineComparator extends AmbiguousComparator<RetraceString> {
+
+    RetraceLineComparator() {
+      super(
+          (line, t) -> {
+            switch (t) {
+              case CLASS:
+                return line.getClassContext().getClassReference().getTypeName();
+              case METHOD:
+                return line.getMethodContext().getMethodReference().getMethodName();
+              case SOURCE:
+                return line.getSource();
+              case LINE:
+                return line.getLineNumber() + "";
+              default:
+                assert false;
+            }
+            throw new RuntimeException("Comparator key is unknown");
+          });
+    }
+  }
+
   private String registerGroups(
       String regularExpression, List<RegularExpressionGroupHandler> handlers) {
     int currentIndex = 0;
@@ -128,6 +171,9 @@
     private final boolean hasTypeOrReturnTypeContext;
     private final String retracedString;
     private final int adjustedIndex;
+    private final boolean isAmbiguous;
+    private final int lineNumber;
+    private final String source;
 
     private RetraceString(
         Element classContext,
@@ -137,7 +183,10 @@
         TypeReference typeOrReturnTypeContext,
         boolean hasTypeOrReturnTypeContext,
         String retracedString,
-        int adjustedIndex) {
+        int adjustedIndex,
+        boolean isAmbiguous,
+        int lineNumber,
+        String source) {
       this.classContext = classContext;
       this.classNameGroup = classNameGroup;
       this.qualifiedContext = qualifiedContext;
@@ -146,6 +195,9 @@
       this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
       this.retracedString = retracedString;
       this.adjustedIndex = adjustedIndex;
+      this.isAmbiguous = isAmbiguous;
+      this.lineNumber = lineNumber;
+      this.source = source;
     }
 
     String getRetracedString() {
@@ -176,6 +228,14 @@
       return RetraceStringBuilder.create(this);
     }
 
+    public int getLineNumber() {
+      return lineNumber;
+    }
+
+    public String getSource() {
+      return source;
+    }
+
     static class RetraceStringBuilder {
 
       private Element classContext;
@@ -186,6 +246,9 @@
       private boolean hasTypeOrReturnTypeContext;
       private String retracedString;
       private int adjustedIndex;
+      private boolean isAmbiguous;
+      private int lineNumber;
+      private String source;
 
       private int maxReplaceStringIndex = NO_MATCH;
 
@@ -197,7 +260,10 @@
           TypeReference typeOrReturnTypeContext,
           boolean hasTypeOrReturnTypeContext,
           String retracedString,
-          int adjustedIndex) {
+          int adjustedIndex,
+          boolean isAmbiguous,
+          int lineNumber,
+          String source) {
         this.classContext = classContext;
         this.classNameGroup = classNameGroup;
         this.qualifiedContext = qualifiedContext;
@@ -206,10 +272,14 @@
         this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
         this.retracedString = retracedString;
         this.adjustedIndex = adjustedIndex;
+        this.isAmbiguous = isAmbiguous;
+        this.lineNumber = lineNumber;
+        this.source = source;
       }
 
       static RetraceStringBuilder create(String string) {
-        return new RetraceStringBuilder(null, null, null, null, null, false, string, 0);
+        return new RetraceStringBuilder(
+            null, null, null, null, null, false, string, 0, false, 0, "");
       }
 
       static RetraceStringBuilder create(RetraceString string) {
@@ -221,7 +291,10 @@
             string.typeOrReturnTypeContext,
             string.hasTypeOrReturnTypeContext,
             string.retracedString,
-            string.adjustedIndex);
+            string.adjustedIndex,
+            string.isAmbiguous,
+            string.lineNumber,
+            string.source);
       }
 
       RetraceStringBuilder setClassContext(Element classContext, ClassNameGroup classNameGroup) {
@@ -246,6 +319,21 @@
         return this;
       }
 
+      RetraceStringBuilder setAmbiguous(boolean isAmbiguous) {
+        this.isAmbiguous = isAmbiguous;
+        return this;
+      }
+
+      RetraceStringBuilder setLineNumber(int lineNumber) {
+        this.lineNumber = lineNumber;
+        return this;
+      }
+
+      RetraceStringBuilder setSource(String source) {
+        this.source = source;
+        return this;
+      }
+
       RetraceStringBuilder replaceInString(String oldString, String newString) {
         int oldStringStartIndex = retracedString.indexOf(oldString);
         assert oldStringStartIndex > NO_MATCH;
@@ -278,7 +366,10 @@
             typeOrReturnTypeContext,
             hasTypeOrReturnTypeContext,
             retracedString,
-            adjustedIndex);
+            adjustedIndex,
+            isAmbiguous,
+            lineNumber,
+            source);
       }
     }
   }
@@ -391,7 +482,7 @@
 
     @Override
     String subExpression() {
-      return javaIdentifierSegment;
+      return "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))";
     }
 
     @Override
@@ -438,6 +529,7 @@
                     }
                     newRetraceString
                         .setMethodContext(element)
+                        .setAmbiguous(element.getRetraceMethodResult().isAmbiguous())
                         .replaceInString(
                             methodReference.getMethodName(),
                             matcher.start(captureGroup),
@@ -513,7 +605,7 @@
 
     @Override
     String subExpression() {
-      return "(?:\\w+\\.)*\\w+";
+      return "(?:(\\w*[\\. ])?(\\w*)?)";
     }
 
     @Override
@@ -540,6 +632,7 @@
           retracedStrings.add(
               retraceString
                   .transform()
+                  .setSource(fileName)
                   .replaceInString(
                       newSourceFile, matcher.start(captureGroup), matcher.end(captureGroup))
                   .build());
@@ -571,12 +664,17 @@
         int lineNumber =
             lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString);
         List<RetraceString> retracedStrings = new ArrayList<>();
+        boolean seenRange = false;
         for (RetraceString retraceString : strings) {
           RetraceMethodResult.Element methodContext = retraceString.methodContext;
-          if (methodContext == null) {
+          if (methodContext == null || methodContext.getMethodReference().isUnknown()) {
             retracedStrings.add(retraceString);
             continue;
           }
+          if (methodContext.hasNoLineNumberRange()) {
+            continue;
+          }
+          seenRange = true;
           Set<MethodReference> narrowedSet =
               methodContext.getRetraceMethodResult().narrowByLine(lineNumber).stream()
                   .map(RetraceMethodResult.Element::getMethodReference)
@@ -588,23 +686,33 @@
                 new StringDiagnostic(
                     "Pruning "
                         + retraceString.getRetracedString()
-                        + " from result because line number "
-                        + lineNumber
-                        + " does not match."));
+                        + " from result because method is not defined on line number "
+                        + lineNumber));
             continue;
           }
-          String newLineNumber =
-              lineNumber > NO_MATCH
-                  ? methodContext.getOriginalLineNumber(lineNumber) + ""
-                  : lineNumberAsString;
+          // The same method can be represented multiple times if it has multiple mappings.
+          if (!methodContext.containsMinifiedLineNumber(lineNumber)) {
+            diagnosticsHandler.info(
+                new StringDiagnostic(
+                    "Pruning "
+                        + retraceString.getRetracedString()
+                        + " from result because method is not in range on line number "
+                        + lineNumber));
+            continue;
+          }
+          int originalLineNumber = methodContext.getOriginalLineNumber(lineNumber);
           retracedStrings.add(
               retraceString
                   .transform()
+                  .setAmbiguous(false)
+                  .setLineNumber(originalLineNumber)
                   .replaceInString(
-                      newLineNumber, matcher.start(captureGroup), matcher.end(captureGroup))
+                      originalLineNumber + "",
+                      matcher.start(captureGroup),
+                      matcher.end(captureGroup))
                   .build());
         }
-        return retracedStrings;
+        return seenRange ? retracedStrings : strings;
       };
     }
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index 7c899a7..3a564d8 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -14,7 +14,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -48,7 +47,8 @@
         assert line.isAtLine();
         AtLine atLine = line.asAtLine();
         if (atLine.isAmbiguous) {
-          strings.add(atLine.toString(previousClazz.isEmpty() ? atLine.at : "or ", previousClazz));
+          strings.add(
+              atLine.toString(previousClazz.isEmpty() ? atLine.at : "<OR> " + atLine.at, ""));
         } else {
           strings.add(atLine.toString());
         }
@@ -57,25 +57,27 @@
     }
   }
 
-  static class AtStackTraceLineComparator implements Comparator<StackTraceLine> {
+  static class AtStackTraceLineComparator extends AmbiguousComparator<StackTraceLine> {
 
-    @Override
-    public int compare(StackTraceLine o1, StackTraceLine o2) {
-      AtLine a1 = (AtLine) o1;
-      AtLine a2 = (AtLine) o2;
-      int compare = a1.clazz.compareTo(a2.clazz);
-      if (compare != 0) {
-        return compare;
-      }
-      compare = a1.method.compareTo(a2.method);
-      if (compare != 0) {
-        return compare;
-      }
-      compare = a1.fileName.compareTo(a2.fileName);
-      if (compare != 0) {
-        return compare;
-      }
-      return Integer.compare(a1.linePosition, a2.linePosition);
+    AtStackTraceLineComparator() {
+      super(
+          (line, t) -> {
+            assert line.isAtLine();
+            AtLine atLine = line.asAtLine();
+            switch (t) {
+              case CLASS:
+                return atLine.clazz;
+              case METHOD:
+                return atLine.method;
+              case SOURCE:
+                return atLine.fileName;
+              case LINE:
+                return atLine.linePosition + "";
+              default:
+                assert false;
+            }
+            throw new RuntimeException("Comparator key is unknown");
+          });
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 701904c..fd3e7f5 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,7 +19,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.Collections;
@@ -39,24 +39,23 @@
     this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
   }
 
-  /**
-   * Used to filter annotations on classes, methods and fields.
-   */
-  private boolean filterAnnotations(DexAnnotation annotation) {
-    return shouldKeepAnnotation(
-        annotation, isAnnotationTypeLive(annotation), appView.dexItemFactory(), appView.options());
+  /** Used to filter annotations on classes, methods and fields. */
+  private boolean filterAnnotations(DexDefinition holder, DexAnnotation annotation) {
+    return shouldKeepAnnotation(holder, annotation, isAnnotationTypeLive(annotation), appView);
   }
 
   static boolean shouldKeepAnnotation(
+      DexDefinition holder,
       DexAnnotation annotation,
       boolean isAnnotationTypeLive,
-      DexItemFactory dexItemFactory,
-      InternalOptions options) {
+      AppView<?> appView) {
     ProguardKeepAttributes config =
-        options.getProguardConfiguration() != null
-            ? options.getProguardConfiguration().getKeepAttributes()
+        appView.options().getProguardConfiguration() != null
+            ? appView.options().getProguardConfiguration().getKeepAttributes()
             : ProguardKeepAttributes.fromPatterns(ImmutableList.of());
 
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+
     switch (annotation.visibility) {
       case DexAnnotation.VISIBILITY_SYSTEM:
         // InnerClass and EnclosingMember are represented in class attributes, not annotations.
@@ -70,9 +69,11 @@
         if (config.signature && DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory)) {
           return true;
         }
-        if (config.sourceDebugExtension
-            && DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
-          return true;
+        if (DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
+          assert holder.isDexClass();
+          appView.setSourceDebugExtensionForType(
+              holder.asDexClass(), annotation.annotation.elements[0].value.asDexValueString());
+          return config.sourceDebugExtension;
         }
         if (config.methodParameters
             && DexAnnotation.isParameterNameAnnotation(annotation, dexItemFactory)) {
@@ -229,25 +230,28 @@
   public void run() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       stripAttributes(clazz);
-      clazz.annotations = clazz.annotations.rewrite(this::rewriteAnnotation);
+      clazz.annotations =
+          clazz.annotations.rewrite(annotation -> rewriteAnnotation(clazz, annotation));
       clazz.forEachMethod(this::processMethod);
       clazz.forEachField(this::processField);
     }
   }
 
   private void processMethod(DexEncodedMethod method) {
-    method.annotations = method.annotations.rewrite(this::rewriteAnnotation);
+    method.annotations =
+        method.annotations.rewrite(annotation -> rewriteAnnotation(method, annotation));
     method.parameterAnnotationsList =
         method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
   }
 
   private void processField(DexEncodedField field) {
-    field.annotations = field.annotations.rewrite(this::rewriteAnnotation);
+    field.annotations =
+        field.annotations.rewrite(annotation -> rewriteAnnotation(field, annotation));
   }
 
-  private DexAnnotation rewriteAnnotation(DexAnnotation original) {
+  private DexAnnotation rewriteAnnotation(DexDefinition holder, DexAnnotation original) {
     // Check if we should keep this annotation first.
-    if (!filterAnnotations(original)) {
+    if (!filterAnnotations(holder, original)) {
       return null;
     }
     // Then, filter out values that refer to dead definitions.
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 0aea415..25a7b15 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -82,7 +82,9 @@
    */
   final SortedSet<DexMethod> targetedMethods;
 
-  final Set<DexMethod> targetedMethodsThatMustRemainNonAbstract;
+  /** Set of targets that lead to resolution errors, such as non-existing or invalid targets. */
+  public final Set<DexMethod> failedResolutionTargets;
+
   /**
    * Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
    */
@@ -117,11 +119,6 @@
    * will have been removed from the code.
    */
   public final Set<DexCallSite> callSites;
-  /**
-   * Set of method signatures used in invoke-super instructions that either cannot be resolved or
-   * resolve to a private method (leading to an IllegalAccessError).
-   */
-  public final SortedSet<DexMethod> brokenSuperInvokes;
   /** Set of all items that have to be kept independent of whether they are used. */
   final Set<DexReference> pinnedItems;
   /** All items with assumemayhavesideeffects rule. */
@@ -189,7 +186,7 @@
       Set<DexType> instantiatedAppServices,
       Set<DexType> instantiatedTypes,
       SortedSet<DexMethod> targetedMethods,
-      Set<DexMethod> targetedMethodsThatMustRemainNonAbstract,
+      Set<DexMethod> failedResolutionTargets,
       SortedSet<DexMethod> bootstrapMethods,
       SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
       SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
@@ -201,7 +198,6 @@
       SortedMap<DexMethod, Set<DexEncodedMethod>> directInvokes,
       SortedMap<DexMethod, Set<DexEncodedMethod>> staticInvokes,
       Set<DexCallSite> callSites,
-      SortedSet<DexMethod> brokenSuperInvokes,
       Set<DexReference> pinnedItems,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
       Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -228,7 +224,7 @@
     this.instantiatedAppServices = instantiatedAppServices;
     this.instantiatedTypes = instantiatedTypes;
     this.targetedMethods = targetedMethods;
-    this.targetedMethodsThatMustRemainNonAbstract = targetedMethodsThatMustRemainNonAbstract;
+    this.failedResolutionTargets = failedResolutionTargets;
     this.bootstrapMethods = bootstrapMethods;
     this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
     this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
@@ -244,7 +240,6 @@
     this.directInvokes = directInvokes;
     this.staticInvokes = staticInvokes;
     this.callSites = callSites;
-    this.brokenSuperInvokes = brokenSuperInvokes;
     this.alwaysInline = alwaysInline;
     this.forceInline = forceInline;
     this.neverInline = neverInline;
@@ -270,7 +265,7 @@
       Set<DexType> instantiatedAppServices,
       Set<DexType> instantiatedTypes,
       SortedSet<DexMethod> targetedMethods,
-      Set<DexMethod> targetedMethodsThatMustRemainNonAbstract,
+      Set<DexMethod> failedResolutionTargets,
       SortedSet<DexMethod> bootstrapMethods,
       SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
       SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
@@ -282,7 +277,6 @@
       SortedMap<DexMethod, Set<DexEncodedMethod>> directInvokes,
       SortedMap<DexMethod, Set<DexEncodedMethod>> staticInvokes,
       Set<DexCallSite> callSites,
-      SortedSet<DexMethod> brokenSuperInvokes,
       Set<DexReference> pinnedItems,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
       Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -309,7 +303,7 @@
     this.instantiatedAppServices = instantiatedAppServices;
     this.instantiatedTypes = instantiatedTypes;
     this.targetedMethods = targetedMethods;
-    this.targetedMethodsThatMustRemainNonAbstract = targetedMethodsThatMustRemainNonAbstract;
+    this.failedResolutionTargets = failedResolutionTargets;
     this.bootstrapMethods = bootstrapMethods;
     this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
     this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
@@ -325,7 +319,6 @@
     this.directInvokes = directInvokes;
     this.staticInvokes = staticInvokes;
     this.callSites = callSites;
-    this.brokenSuperInvokes = brokenSuperInvokes;
     this.alwaysInline = alwaysInline;
     this.forceInline = forceInline;
     this.neverInline = neverInline;
@@ -352,7 +345,7 @@
         previous.instantiatedAppServices,
         previous.instantiatedTypes,
         previous.targetedMethods,
-        previous.targetedMethodsThatMustRemainNonAbstract,
+        previous.failedResolutionTargets,
         previous.bootstrapMethods,
         previous.methodsTargetedByInvokeDynamic,
         previous.virtualMethodsTargetedByInvokeDirect,
@@ -364,7 +357,6 @@
         previous.directInvokes,
         previous.staticInvokes,
         previous.callSites,
-        previous.brokenSuperInvokes,
         previous.pinnedItems,
         previous.mayHaveSideEffects,
         previous.noSideEffects,
@@ -400,7 +392,7 @@
         previous.instantiatedAppServices,
         previous.instantiatedTypes,
         previous.targetedMethods,
-        previous.targetedMethodsThatMustRemainNonAbstract,
+        previous.failedResolutionTargets,
         previous.bootstrapMethods,
         previous.methodsTargetedByInvokeDynamic,
         previous.virtualMethodsTargetedByInvokeDirect,
@@ -412,7 +404,6 @@
         previous.directInvokes,
         previous.staticInvokes,
         previous.callSites,
-        previous.brokenSuperInvokes,
         additionalPinnedItems == null
             ? previous.pinnedItems
             : CollectionUtils.mergeSets(previous.pinnedItems, additionalPinnedItems),
@@ -452,8 +443,8 @@
     this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
     this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
     this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
-    this.targetedMethodsThatMustRemainNonAbstract =
-        lense.rewriteMethodsConservatively(previous.targetedMethodsThatMustRemainNonAbstract);
+    this.failedResolutionTargets =
+        lense.rewriteMethodsConservatively(previous.failedResolutionTargets);
     this.bootstrapMethods = lense.rewriteMethodsConservatively(previous.bootstrapMethods);
     this.methodsTargetedByInvokeDynamic =
         lense.rewriteMethodsConservatively(previous.methodsTargetedByInvokeDynamic);
@@ -481,7 +472,6 @@
     // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
     // after second tree shaking.
     this.callSites = previous.callSites;
-    this.brokenSuperInvokes = lense.rewriteMethodsConservatively(previous.brokenSuperInvokes);
     // Don't rewrite pruned types - the removed types are identified by their original name.
     this.prunedTypes = previous.prunedTypes;
     this.mayHaveSideEffects =
@@ -535,8 +525,7 @@
     this.instantiatedTypes = previous.instantiatedTypes;
     this.instantiatedLambdas = previous.instantiatedLambdas;
     this.targetedMethods = previous.targetedMethods;
-    this.targetedMethodsThatMustRemainNonAbstract =
-        previous.targetedMethodsThatMustRemainNonAbstract;
+    this.failedResolutionTargets = previous.failedResolutionTargets;
     this.bootstrapMethods = previous.bootstrapMethods;
     this.methodsTargetedByInvokeDynamic = previous.methodsTargetedByInvokeDynamic;
     this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
@@ -552,7 +541,6 @@
     this.directInvokes = previous.directInvokes;
     this.staticInvokes = previous.staticInvokes;
     this.callSites = previous.callSites;
-    this.brokenSuperInvokes = previous.brokenSuperInvokes;
     this.alwaysInline = previous.alwaysInline;
     this.forceInline = previous.forceInline;
     this.neverInline = previous.neverInline;
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 8329fcf..6bd3db2 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -156,11 +156,6 @@
   private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
 
   /**
-   * Set of method signatures used in invoke-super instructions that either cannot be resolved or
-   * resolve to a private method (leading to an IllegalAccessError).
-   */
-  private final Set<DexMethod> brokenSuperInvokes = Sets.newIdentityHashSet();
-  /**
    * This map keeps a view of all virtual methods that are reachable from virtual invokes. A method
    * is reachable even if no live subtypes exist, so this is not sufficient for inclusion in the
    * live set.
@@ -211,8 +206,8 @@
    */
   private final SetWithReason<DexEncodedMethod> targetedMethods;
 
-  /** Subset of 'targetedMethods' for which the method must not be marked abstract. */
-  private final Set<DexEncodedMethod> targetedMethodsThatMustRemainNonAbstract;
+  /** Set of methods that have invalid resolutions or lookups. */
+  private final Set<DexMethod> failedResolutionTargets;
 
   /**
    * Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
@@ -329,7 +324,7 @@
     // This set is only populated in edge cases due to multiple default interface methods.
     // The set is generally expected to be empty and in the unlikely chance it is not, it will
     // likely contain two methods. Thus the default capacity of 2.
-    targetedMethodsThatMustRemainNonAbstract = SetUtils.newIdentityHashSet(2);
+    failedResolutionTargets = SetUtils.newIdentityHashSet(2);
     liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
     liveFields = new SetWithReason<>(graphReporter::registerField);
     instantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
@@ -1313,7 +1308,7 @@
     DexClass clazz = appView.definitionFor(type);
     boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
     boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
-    if (!shouldKeepAnnotation(annotation, isLive, appView.dexItemFactory(), options)) {
+    if (!shouldKeepAnnotation(holder, annotation, isLive, appView)) {
       // Remember this annotation for later.
       if (!annotationTypeIsLibraryClass) {
         deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
@@ -1327,21 +1322,22 @@
     annotation.annotation.collectIndexedItems(referenceMarker);
   }
 
-  private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
+  private ResolutionResult resolveMethod(DexMethod method, KeepReason reason) {
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    if (resolutionResult == null) {
+    if (resolutionResult.isFailedResolution()) {
       reportMissingMethod(method);
+      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
+    }
+    return resolutionResult;
+  }
+
+  private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
+    SingleResolutionResult resolution = resolveMethod(method, reason).asSingleResolution();
+    if (resolution == null || resolution.getResolvedHolder().isNotProgramClass()) {
       return;
     }
-    DexEncodedMethod encodedMethod = resolutionResult.getSingleTarget();
-    if (encodedMethod == null) {
-      // Note: should this be reported too? Or is this unreachable?
-      return;
-    }
-    DexProgramClass clazz = getProgramClassOrNull(encodedMethod.method.holder);
-    if (clazz == null) {
-      return;
-    }
+    DexProgramClass clazz = resolution.getResolvedHolder().asProgramClass();
+    DexEncodedMethod encodedMethod = resolution.getResolvedMethod();
 
     // We have to mark the resolved method as targeted even if it cannot actually be invoked
     // to make sure the invocation will keep failing in the appropriate way.
@@ -1636,7 +1632,7 @@
     assert libraryClass.isNotProgramClass();
     assert !instantiatedClass.isInterface() || instantiatedClass.accessFlags.isAnnotation();
     for (DexEncodedMethod method : libraryClass.virtualMethods()) {
-      // Note: it may be worthwhile to add a resolution cache here. If so, it must till ensure
+      // Note: it may be worthwhile to add a resolution cache here. If so, it must still ensure
       // that all library override edges are reported to the kept-graph consumer.
       ResolutionResult firstResolution =
           appView.appInfo().resolveMethod(instantiatedClass, method.method);
@@ -2100,7 +2096,7 @@
         appInfo.resolveMethod(method.holder, method, interfaceInvoke);
     if (resolutionResult.isFailedResolution()) {
       // If the resolution fails, mark each dependency causing a failure.
-      markFailedResolutionTargets(resolutionResult.asFailedResolution(), reason);
+      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
       return MarkedResolutionTarget.unresolved();
     }
 
@@ -2132,12 +2128,13 @@
   }
 
   private void markFailedResolutionTargets(
-      FailedResolutionResult failedResolution, KeepReason reason) {
+      DexMethod symbolicMethod, FailedResolutionResult failedResolution, KeepReason reason) {
+    failedResolutionTargets.add(symbolicMethod);
     failedResolution.forEachFailureDependency(
         method -> {
           DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
           if (clazz != null) {
-            targetedMethodsThatMustRemainNonAbstract.add(method);
+            failedResolutionTargets.add(method.method);
             markMethodAsTargeted(clazz, method, reason);
           }
         });
@@ -2168,27 +2165,22 @@
 
   // Package protected due to entry point from worklist.
   void markSuperMethodAsReachable(DexMethod method, DexEncodedMethod from) {
-    // If the method does not resolve, mark it broken to avoid hiding errors in other optimizations.
-    SingleResolutionResult resolution =
-        appInfo.resolveMethod(method.holder, method).asSingleResolution();
+    KeepReason reason = KeepReason.targetedBySuperFrom(from);
+    SingleResolutionResult resolution = resolveMethod(method, reason).asSingleResolution();
     if (resolution == null) {
-      brokenSuperInvokes.add(method);
-      reportMissingMethod(method);
       return;
     }
     // If the resolution is in the program, mark it targeted.
     if (resolution.getResolvedHolder().isProgramClass()) {
       markMethodAsTargeted(
-          resolution.getResolvedHolder().asProgramClass(),
-          resolution.getResolvedMethod(),
-          KeepReason.targetedBySuperFrom(from));
+          resolution.getResolvedHolder().asProgramClass(), resolution.getResolvedMethod(), reason);
     }
     // If invoke target is invalid (inaccessible or not an instance-method) record it and stop.
     // TODO(b/146016987): We should be passing the full program context and not looking it up again.
     DexProgramClass fromHolder = appInfo.definitionFor(from.method.holder).asProgramClass();
     DexEncodedMethod target = resolution.lookupInvokeSuperTarget(fromHolder, appInfo);
     if (target == null) {
-      brokenSuperInvokes.add(resolution.getResolvedMethod().method);
+      failedResolutionTargets.add(resolution.getResolvedMethod().method);
       return;
     }
 
@@ -2281,8 +2273,7 @@
             Collections.unmodifiableSet(instantiatedAppServices),
             SetUtils.mapIdentityHashSet(instantiatedTypes.getItems(), DexProgramClass::getType),
             Enqueuer.toSortedDescriptorSet(targetedMethods.getItems()),
-            SetUtils.mapIdentityHashSet(
-                targetedMethodsThatMustRemainNonAbstract, DexEncodedMethod::getKey),
+            Collections.unmodifiableSet(failedResolutionTargets),
             ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, bootstrapMethods),
             ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, methodsTargetedByInvokeDynamic),
             ImmutableSortedSet.copyOf(
@@ -2297,7 +2288,6 @@
             toImmutableSortedMap(directInvokes, PresortedComparable::slowCompare),
             toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
             callSites,
-            ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, brokenSuperInvokes),
             pinnedItems,
             rootSet.mayHaveSideEffects,
             rootSet.noSideEffects,
@@ -2642,6 +2632,7 @@
     markParameterAndReturnTypesAsLive(method);
   }
 
+
   private void markParameterAndReturnTypesAsLive(DexEncodedMethod method) {
     for (DexType parameterType : method.method.proto.parameters.values) {
       markTypeAsLive(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index e7a7fe5..116e315 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -297,7 +297,7 @@
                 && !method.accessFlags.isSynchronized()
                 && !method.accessFlags.isPrivate()
                 && !method.accessFlags.isStatic()
-                && !appInfo.targetedMethodsThatMustRemainNonAbstract.contains(method.method);
+                && !appInfo.failedResolutionTargets.contains(method.method);
         // Private methods and static methods can only be targeted yet non-live as the result of
         // an invalid invoke. They will not actually be called at runtime but we have to keep them
         // as non-abstract (see above) to produce the same failure mode.
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 9a477cc..c518937 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -263,24 +263,6 @@
       }
     }
 
-    // Avoid merging two types if this could remove a NoSuchMethodError, as illustrated by the
-    // following example. (Alternatively, it would be possible to merge A and B and rewrite the
-    // "invoke-super A.m" instruction into "invoke-super Object.m" to preserve the error. This
-    // situation should generally not occur in practice, though.)
-    //
-    //   class A {}
-    //   class B extends A {
-    //     public void m() {}
-    //   }
-    //   class C extends A {
-    //     public void m() {
-    //       invoke-super "A.m" <- should yield NoSuchMethodError, cannot merge A and B
-    //     }
-    //   }
-    for (DexMethod signature : appInfo.brokenSuperInvokes) {
-      markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_SUPER);
-    }
-
     // It is valid to have an invoke-direct instruction in a default interface method that targets
     // another default method in the same interface (see InterfaceMethodDesugaringTests.testInvoke-
     // SpecialToDefaultMethod). However, in a class, that would lead to a verification error.
@@ -290,7 +272,7 @@
     }
 
     // The set of targets that must remain for proper resolution error cases should not be merged.
-    for (DexMethod method : appInfo.targetedMethodsThatMustRemainNonAbstract) {
+    for (DexMethod method : appInfo.failedResolutionTargets) {
       markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
new file mode 100644
index 0000000..94024ee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
@@ -0,0 +1,107 @@
+// 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.utils;
+
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfLineToMethodMapper {
+
+  private final Map<String, Int2ReferenceOpenHashMap<String>> sourceMethodMapping = new HashMap<>();
+  private final AndroidApp inputApp;
+  private static final String NAME_DESCRIPTOR_SEPARATOR = ";;";
+
+  public CfLineToMethodMapper(AndroidApp inputApp) {
+    this.inputApp = inputApp;
+  }
+
+  public String lookupNameAndDescriptor(String binaryName, int lineNumber)
+      throws IOException, ResourceException {
+    if (sourceMethodMapping.isEmpty()) {
+      readLineNumbersFromClassFiles();
+    }
+    Int2ReferenceOpenHashMap<String> lineMappings = sourceMethodMapping.get(binaryName);
+    return lineMappings == null ? null : lineMappings.get(lineNumber);
+  }
+
+  private void readLineNumbersFromClassFiles() throws ResourceException, IOException {
+    ClassVisitor classVisitor = new ClassVisitor();
+    for (ProgramResourceProvider resourceProvider : inputApp.getProgramResourceProviders()) {
+      for (ProgramResource programResource : resourceProvider.getProgramResources()) {
+        if (programResource.getKind() == Kind.CF) {
+          new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
+              .accept(classVisitor, ClassReader.SKIP_FRAMES);
+        }
+      }
+    }
+  }
+
+  public static String getName(String nameAndDescriptor) {
+    int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR);
+    assert index > 0;
+    return nameAndDescriptor.substring(0, index);
+  }
+
+  public static String getDescriptor(String nameAndDescriptor) {
+    int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR);
+    assert index > 0;
+    return nameAndDescriptor.substring(index + NAME_DESCRIPTOR_SEPARATOR.length());
+  }
+
+  private class ClassVisitor extends org.objectweb.asm.ClassVisitor {
+
+    private Int2ReferenceOpenHashMap<String> currentLineNumberMapping = null;
+
+    private ClassVisitor() {
+      super(InternalOptions.ASM_VERSION);
+    }
+
+    @Override
+    public void visit(
+        int version,
+        int access,
+        String name,
+        String signature,
+        String superName,
+        String[] interfaces) {
+      super.visit(version, access, name, signature, superName, interfaces);
+      currentLineNumberMapping =
+          sourceMethodMapping.computeIfAbsent(name, ignored -> new Int2ReferenceOpenHashMap<>());
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String descriptor, String signature, String[] exceptions) {
+      return new MethodLineVisitor(
+          name + NAME_DESCRIPTOR_SEPARATOR + descriptor, currentLineNumberMapping);
+    }
+  }
+
+  private static class MethodLineVisitor extends MethodVisitor {
+
+    private final String nameAndDescriptor;
+    private final Map<Integer, String> lineMethodMapping;
+
+    private MethodLineVisitor(String nameAndDescriptor, Map<Integer, String> lineMethodMapping) {
+      super(InternalOptions.ASM_VERSION);
+      this.nameAndDescriptor = nameAndDescriptor;
+      this.lineMethodMapping = lineMethodMapping;
+    }
+
+    @Override
+    public void visitLineNumber(int line, Label start) {
+      lineMethodMapping.put(line, nameAndDescriptor);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index c3916cb..dccd176 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -16,6 +16,8 @@
 import java.io.File;
 import java.nio.file.Path;
 import java.util.Map;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeVisitor;
 
 public class DescriptorUtils {
 
@@ -355,6 +357,36 @@
   }
 
   /**
+   * Get a fully qualified name from a classifier in Kotlin metadata.
+   * @param kmType where classifier contains Kotlin internal name, like "org/foo/bar/Baz.Nested"
+   * @return a class descriptor like "Lorg/foo/bar/Baz$Nested;"
+   */
+  public static String getDescriptorFromKmType(KmType kmType) {
+    if (kmType == null) {
+      return null;
+    }
+    Box<String> descriptor = new Box<>(null);
+    kmType.accept(new KmTypeVisitor() {
+      @Override
+      public void visitClass(String name) {
+        // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
+        //  See b/70169921#comment25 for more details.
+        String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
+        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+      }
+
+      @Override
+      public void visitTypeAlias(String name) {
+        // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
+        //  See b/70169921#comment25 for more details.
+        String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
+        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+      }
+    });
+    return descriptor.get();
+  }
+
+  /**
    * Get unqualified class name from its binary name.
    *
    * @param classBinaryName a class binary name i.e. "java/lang/Object" or "a/b/C$Inner"
@@ -550,4 +582,76 @@
     assert classDescriptor != null && isClassDescriptor(classDescriptor);
     return getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
   }
+
+  public static String getReturnTypeDescriptor(final String methodDescriptor) {
+    assert methodDescriptor.indexOf(')') != -1;
+    return methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
+  }
+
+  public static String getShortyDescriptor(String descriptor) {
+    if (descriptor.length() == 1) {
+      return descriptor;
+    }
+    assert descriptor.charAt(0) == 'L' || descriptor.charAt(0) == '[';
+    return "L";
+  }
+
+  public static String[] getArgumentTypeDescriptors(final String methodDescriptor) {
+    String[] argDescriptors = new String[getArgumentCount(methodDescriptor)];
+    int charIdx = 1;
+    char c;
+    int argIdx = 0;
+    int startType;
+    while ((c = methodDescriptor.charAt(charIdx)) != ')') {
+      switch (c) {
+        case 'V':
+          throw new Unreachable();
+        case 'Z':
+        case 'C':
+        case 'B':
+        case 'S':
+        case 'I':
+        case 'F':
+        case 'J':
+        case 'D':
+          argDescriptors[argIdx++] = Character.toString(c);
+          break;
+        case '[':
+          startType = charIdx;
+          while (methodDescriptor.charAt(++charIdx) == '[') {}
+          if (methodDescriptor.charAt(charIdx) == 'L') {
+            while (methodDescriptor.charAt(++charIdx) != ';')
+              ;
+          }
+          argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
+          break;
+        case 'L':
+          startType = charIdx;
+          while (methodDescriptor.charAt(++charIdx) != ';')
+            ;
+          argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
+          break;
+        default:
+          throw new Unreachable();
+      }
+      charIdx++;
+    }
+    return argDescriptors;
+  }
+
+  public static int getArgumentCount(final String methodDescriptor) {
+    int charIdx = 1;
+    char c;
+    int argCount = 0;
+    while ((c = methodDescriptor.charAt(charIdx++)) != ')') {
+      if (c == 'L') {
+        while (methodDescriptor.charAt(charIdx++) != ';')
+          ;
+        argCount++;
+      } else if (c != '[') {
+        argCount++;
+      }
+    }
+    return argCount;
+  }
 }
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 c4815ae..9b47305 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -5,7 +5,7 @@
 
 import static com.google.common.base.Predicates.not;
 
-import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.AssertionsConfiguration;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DataResourceConsumer;
@@ -216,7 +216,6 @@
   public boolean enablePropagationOfConstantsAtCallSites = false;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
-  public boolean enableSourceDebugExtensionRewriter = false;
   public boolean enableCfInterfaceMethodDesugaring = false;
 
   public int callGraphLikelySpuriousCallEdgeThreshold = 50;
@@ -435,7 +434,7 @@
   public boolean ignoreMissingClasses = false;
   // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
   public boolean forceProguardCompatibility = false;
-  public AssertionTransformation assertionTransformation = null;
+  public AssertionsConfiguration assertionsConfiguration = null;
   public boolean configurationDebugging = false;
 
   // Don't convert Code objects to IRCode.
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index bdfabb0..933f4fb 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -10,6 +11,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
@@ -31,8 +33,11 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNaming;
 import com.android.tools.r8.naming.ClassNaming.Builder;
@@ -43,6 +48,7 @@
 import com.android.tools.r8.naming.Range;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.base.Suppliers;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -56,15 +62,14 @@
   // PositionRemapper is a stateful function which takes a position (represented by a
   // DexDebugPositionState) and returns a remapped Position.
   private interface PositionRemapper {
-    Position createRemappedPosition(
-        int line, DexString file, DexMethod method, Position callerPosition);
+    Pair<Position, Position> createRemappedPosition(Position position);
   }
 
   private static class IdentityPositionRemapper implements PositionRemapper {
+
     @Override
-    public Position createRemappedPosition(
-        int line, DexString file, DexMethod method, Position callerPosition) {
-      return new Position(line, file, method, callerPosition);
+    public Pair<Position, Position> createRemappedPosition(Position position) {
+      return new Pair<>(position, position);
     }
   }
 
@@ -82,21 +87,116 @@
     }
 
     @Override
-    public Position createRemappedPosition(
-        int line, DexString file, DexMethod method, Position callerPosition) {
-      assert method != null;
-      if (previousMethod == method) {
+    public Pair<Position, Position> createRemappedPosition(Position position) {
+      assert position.method != null;
+      if (previousMethod == position.method) {
         assert previousSourceLine >= 0;
-        if (line > previousSourceLine && line - previousSourceLine <= maxLineDelta) {
-            nextOptimizedLineNumber += (line - previousSourceLine) - 1;
+        if (position.line > previousSourceLine
+            && position.line - previousSourceLine <= maxLineDelta) {
+          nextOptimizedLineNumber += (position.line - previousSourceLine) - 1;
         }
       }
 
-      Position newPosition = new Position(nextOptimizedLineNumber, file, method, null);
+      Position newPosition =
+          new Position(nextOptimizedLineNumber, position.file, position.method, null);
       ++nextOptimizedLineNumber;
-      previousSourceLine = line;
-      previousMethod = method;
-      return newPosition;
+      previousSourceLine = position.line;
+      previousMethod = position.method;
+      return new Pair<>(position, newPosition);
+    }
+  }
+
+  private static class KotlinInlineFunctionPositionRemapper implements PositionRemapper {
+
+    private final AppView<?> appView;
+    private final DexItemFactory factory;
+    private final Map<DexType, Result> parsedKotlinSourceDebugExtensions = new IdentityHashMap<>();
+    private final CfLineToMethodMapper lineToMethodMapper;
+    private final PositionRemapper baseRemapper;
+
+    // Fields for the current context.
+    private DexEncodedMethod currentMethod;
+    private Result parsedData = null;
+
+    private KotlinInlineFunctionPositionRemapper(
+        AppView<?> appView,
+        AndroidApp inputApp,
+        PositionRemapper baseRemapper,
+        CfLineToMethodMapper lineToMethodMapper) {
+      this.appView = appView;
+      this.factory = appView.dexItemFactory();
+      this.baseRemapper = baseRemapper;
+      this.lineToMethodMapper = lineToMethodMapper;
+    }
+
+    @Override
+    public Pair<Position, Position> createRemappedPosition(Position position) {
+      assert currentMethod != null;
+      int line = position.line;
+      Result parsedData = getAndParseSourceDebugExtension(position.method.holder);
+      if (parsedData == null) {
+        return baseRemapper.createRemappedPosition(position);
+      }
+      Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> currentPosition =
+          parsedData.lookup(line);
+      if (currentPosition == null) {
+        return baseRemapper.createRemappedPosition(position);
+      }
+      int delta = line - currentPosition.getKey();
+      int originalPosition = currentPosition.getValue().getRange().from + delta;
+      try {
+        String binaryName = currentPosition.getValue().getSource().getPath();
+        String nameAndDescriptor =
+            lineToMethodMapper.lookupNameAndDescriptor(binaryName, originalPosition);
+        if (nameAndDescriptor == null) {
+          return baseRemapper.createRemappedPosition(position);
+        }
+        String clazzDescriptor = DescriptorUtils.getDescriptorFromClassBinaryName(binaryName);
+        String methodName = CfLineToMethodMapper.getName(nameAndDescriptor);
+        String methodDescriptor = CfLineToMethodMapper.getDescriptor(nameAndDescriptor);
+        String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(methodDescriptor);
+        String[] argumentDescriptors = DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor);
+        DexString[] argumentDexStringDescriptors = new DexString[argumentDescriptors.length];
+        for (int i = 0; i < argumentDescriptors.length; i++) {
+          argumentDexStringDescriptors[i] = factory.createString(argumentDescriptors[i]);
+        }
+        DexMethod inlinee =
+            factory.createMethod(
+                factory.createString(clazzDescriptor),
+                factory.createString(methodName),
+                factory.createString(returnTypeDescriptor),
+                argumentDexStringDescriptors);
+        if (!inlinee.equals(position.method)) {
+          return baseRemapper.createRemappedPosition(
+              new Position(originalPosition, null, inlinee, position));
+        }
+        // This is the same position, so we should really not mark this as an inline position. Fall
+        // through to the default case.
+      } catch (IOException | ResourceException ignored) {
+        // Intentionally left empty. Remapping of kotlin functions utility is a best effort mapping.
+      }
+      return baseRemapper.createRemappedPosition(position);
+    }
+
+    private Result getAndParseSourceDebugExtension(DexType holder) {
+      if (parsedData == null) {
+        parsedData = parsedKotlinSourceDebugExtensions.get(holder);
+      }
+      if (parsedData != null || parsedKotlinSourceDebugExtensions.containsKey(holder)) {
+        return parsedData;
+      }
+      DexClass clazz = appView.definitionFor(currentMethod.method.holder);
+      DexValueString dexValueString = appView.getSourceDebugExtensionForType(clazz);
+      if (dexValueString != null) {
+        parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
+      }
+      parsedKotlinSourceDebugExtensions.put(holder, parsedData);
+      return parsedData;
+    }
+
+    public void setMethod(DexEncodedMethod method) {
+      this.currentMethod = method;
+      this.parsedData = null;
     }
   }
 
@@ -163,7 +263,11 @@
   public static ClassNameMapper run(
       AppView<AppInfoWithSubtyping> appView,
       DexApplication application,
+      AndroidApp inputApp,
       NamingLens namingLens) {
+    // For finding methods in kotlin files based on SourceDebugExtensions, we use a line method map.
+    // We create it here to ensure it is only reading class files once.
+    CfLineToMethodMapper cfLineToMethodMapper = new CfLineToMethodMapper(inputApp);
     ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
     // Collect which files contain which classes that need to have their line numbers optimized.
     for (DexProgramClass clazz : application.classes()) {
@@ -209,15 +313,23 @@
                 ? new IdentityPositionRemapper()
                 : new OptimizingPositionRemapper(appView.options());
 
+        // Kotlin inline functions and arguments have their inlining information stored in the
+        // source debug extension annotation. Instantiate the kotlin remapper on top of the original
+        // remapper to allow for remapping original positions to kotlin inline positions.
+        KotlinInlineFunctionPositionRemapper kotlinRemapper =
+            new KotlinInlineFunctionPositionRemapper(
+                appView, inputApp, positionRemapper, cfLineToMethodMapper);
+
         for (DexEncodedMethod method : methods) {
+          kotlinRemapper.currentMethod = method;
           List<MappedPosition> mappedPositions = new ArrayList<>();
           Code code = method.getCode();
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
               optimizeDexCodePositions(
-                  method, application, positionRemapper, mappedPositions, identityMapping);
+                  method, application, kotlinRemapper, mappedPositions, identityMapping);
             } else if (code.isCfCode() && doesContainPositions(code.asCfCode())) {
-              optimizeCfCodePositions(method, positionRemapper, mappedPositions, appView);
+              optimizeCfCodePositions(method, kotlinRemapper, mappedPositions, appView);
             }
           }
 
@@ -434,6 +546,8 @@
     PositionEventEmitter positionEventEmitter =
         new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
 
+    Box<Boolean> inlinedOriginalPosition = new Box<>(false);
+
     // Debug event visitor to map line numbers.
     DexDebugEventVisitor visitor =
         new DexDebugPositionState(debugInfo.startLine, method.method) {
@@ -455,18 +569,16 @@
             super.visit(defaultEvent);
             assert getCurrentLine() >= 0;
             Position position =
-                positionRemapper.createRemappedPosition(
+                new Position(
                     getCurrentLine(),
                     getCurrentFile(),
                     getCurrentMethod(),
                     getCurrentCallerPosition());
-            mappedPositions.add(
-                new MappedPosition(
-                    getCurrentMethod(),
-                    getCurrentLine(),
-                    getCurrentCallerPosition(),
-                    position.line));
-            positionEventEmitter.emitPositionEvents(getCurrentPc(), position);
+            Position currentPosition = remapAndAdd(position, positionRemapper, mappedPositions);
+            if (currentPosition != position) {
+              inlinedOriginalPosition.set(true);
+            }
+            positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
             emittedPc = getCurrentPc();
           }
 
@@ -522,7 +634,7 @@
 
     // TODO(b/111253214) Remove this as soon as we have external tests testing not only the
     // remapping but whether the non-positional debug events remain intact.
-    if (identityMapping) {
+    if (identityMapping && !inlinedOriginalPosition.get()) {
       assert optimizedDebugInfo.startLine == debugInfo.startLine;
       assert optimizedDebugInfo.events.length == debugInfo.events.length;
       for (int i = 0; i < debugInfo.events.length; ++i) {
@@ -546,17 +658,10 @@
       CfInstruction newInstruction;
       if (oldInstruction instanceof CfPosition) {
         CfPosition cfPosition = (CfPosition) oldInstruction;
-        Position oldPosition = cfPosition.getPosition();
-        Position newPosition =
-            positionRemapper.createRemappedPosition(
-                oldPosition.line, oldPosition.file, oldPosition.method, oldPosition.callerPosition);
-        mappedPositions.add(
-            new MappedPosition(
-                oldPosition.method,
-                oldPosition.line,
-                oldPosition.callerPosition,
-                newPosition.line));
-        newInstruction = new CfPosition(cfPosition.getLabel(), newPosition);
+        newInstruction =
+            new CfPosition(
+                cfPosition.getLabel(),
+                remapAndAdd(cfPosition.getPosition(), positionRemapper, mappedPositions));
       } else {
         newInstruction = oldInstruction;
       }
@@ -572,4 +677,15 @@
             oldCode.getLocalVariables()),
         appView);
   }
+
+  private static Position remapAndAdd(
+      Position position, PositionRemapper remapper, List<MappedPosition> mappedPositions) {
+    Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
+    Position oldPosition = remappedPosition.getFirst();
+    Position newPosition = remappedPosition.getSecond();
+    mappedPositions.add(
+        new MappedPosition(
+            oldPosition.method, oldPosition.line, oldPosition.callerPosition, newPosition.line));
+    return newPosition;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 44da91c..d602a51 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -10,12 +10,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
-import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.references.Reference;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -90,17 +85,13 @@
 
   private static DexProgramClass mergeClasses(
       Reporter reporter, DexProgramClass a, DexProgramClass b) {
-    if (LambdaRewriter.hasLambdaClassPrefix(a.type)
-        || BackportedMethodRewriter.hasRewrittenMethodPrefix(a.type)
-        || InterfaceMethodRewriter.hasDispatchClassSuffix(a.type)
-        || NestBasedAccessDesugaring.isNestConstructor(a.type)
-        || TwrCloseResourceRewriter.isUtilityClassDescriptor(a.type)) {
-      assert assertEqualClasses(a, b);
-      return a;
-    }
     if (DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(a.type)) {
       return mergeWrappers(a, b);
     }
+    if (a.type.isD8R8SynthesizedClassType()) {
+      assert assertEqualClasses(a, b);
+      return a;
+    }
     throw reportDuplicateTypes(reporter, a, b);
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 265d333..fdf7e98 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -318,4 +318,13 @@
     assert s.length() > charsFromEnd;
     return s.charAt(s.length() - (charsFromEnd + 1));
   }
+
+  public static int firstNonWhitespaceCharacter(String string) {
+    for (int i = 0; i < string.length(); i++) {
+      if (!isWhitespace(string.charAt(i))) {
+        return i;
+      }
+    }
+    return string.length();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index bef8a14..0e510a9 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -22,6 +22,7 @@
 
   // Ordered list of classpath entries.
   private List<Path> classpath = new ArrayList<>();
+  private List<String> vmArguments = new ArrayList<>();
 
   private AndroidApp.Builder builder = AndroidApp.builder();
 
@@ -49,7 +50,8 @@
       throws IOException {
     assert runtime.isCf();
     ProcessResult result =
-        ToolHelper.runJava(runtime.asCf(), classpath, ObjectArrays.concat(mainClass, args));
+        ToolHelper.runJava(
+            runtime.asCf(), vmArguments, classpath, ObjectArrays.concat(mainClass, args));
     return new JvmTestRunResult(builder.build(), runtime, result);
   }
 
@@ -136,4 +138,13 @@
   public JvmTestBuilder addTestClasspath() {
     return addClasspath(ToolHelper.getClassPathForTests());
   }
+
+  public JvmTestBuilder addVmArguments(Collection<String> arguments) {
+    vmArguments.addAll(arguments);
+    return self();
+  }
+
+  public JvmTestBuilder addVmArguments(String... arguments) {
+    return addVmArguments(Arrays.asList(arguments));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 0f613d5..cc8d46a 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -118,6 +118,7 @@
   }
 
   @Override
+  @Deprecated
   public RR run(String mainClass)
       throws CompilationFailedException, ExecutionException, IOException {
     return compile().run(mainClass);
diff --git a/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java b/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
index a346177..d7cdcd0 100644
--- a/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
+++ b/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
@@ -9,8 +9,7 @@
     lambda.consume("TestDesugar.consume");
   }
 
-
-  public interface Consumer {
+  interface Consumer {
     public void consume(String s);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 7fab7a1..cf013d1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -733,11 +733,6 @@
           CF_DIR.resolve("SuperClassWithReferencedMethod.class"),
           CF_DIR.resolve("SuperCallRewritingTest.class")
         };
-    Set<String> preservedClassNames =
-        ImmutableSet.of(
-            "classmerging.SubClassThatReferencesSuperMethod",
-            "classmerging.SuperClassWithReferencedMethod",
-            "classmerging.SuperCallRewritingTest");
 
     // Build SubClassThatReferencesMethod.
     SmaliBuilder smaliBuilder =
@@ -761,19 +756,28 @@
         "move-result-object v1",
         "return-object v1");
 
-    // Build app.
-    AndroidApp.Builder builder = AndroidApp.builder();
-    builder.addProgramFiles(programFiles);
-    builder.addDexProgramData(smaliBuilder.compile(), Origin.unknown());
+    String expectedOutput =
+        StringUtils.lines(
+            "Calling referencedMethod on SubClassThatReferencesSuperMethod",
+            "In referencedMethod on SubClassThatReferencesSuperMethod",
+            "In referencedMethod on SuperClassWithReferencedMethod",
+            "SuperClassWithReferencedMethod.referencedMethod()");
 
-    // Run test.
-    runTestOnInput(
-        main,
-        builder.build(),
-        preservedClassNames::contains,
-        // Prevent class merging, such that the generated code would be invalid if we rewrite the
-        // invoke-super instruction into an invoke-direct instruction.
-        getProguardConfig(EXAMPLE_KEEP, "-keep class *"));
+    testForD8()
+        .addProgramFiles(programFiles)
+        .addProgramDexFileData(smaliBuilder.compile())
+        .run(main)
+        .assertSuccessWithOutput(expectedOutput);
+
+    testForR8(Backend.DEX)
+        .addOptionsModification(this::configure)
+        .addKeepMainRule(main)
+        // Keep the classes to avoid merge, but don't keep methods which allows inlining.
+        .addKeepRules("-keep class *")
+        .addProgramFiles(programFiles)
+        .addProgramDexFileData(smaliBuilder.compile())
+        .run(main)
+        .assertSuccessWithOutput(expectedOutput);
   }
 
   // The following test checks that our rewriting of invoke-super instructions in a class F works
diff --git a/src/test/java/com/android/tools/r8/desugar/BackportedMethodMergeTest.java b/src/test/java/com/android/tools/r8/desugar/BackportedMethodMergeTest.java
index 44f32ee..f71139f 100644
--- a/src/test/java/com/android/tools/r8/desugar/BackportedMethodMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/BackportedMethodMergeTest.java
@@ -4,9 +4,14 @@
 
 package com.android.tools.r8.desugar;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.transformers.ClassTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
 import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class BackportedMethodMergeTest extends TestBase {
@@ -40,6 +45,61 @@
         .assertSuccessWithOutput(jvmOutput);
   }
 
+  @Test
+  public void testMergeOldPrefix()
+      throws IOException, CompilationFailedException, ExecutionException {
+    byte[] transform = transformer($r8$java8methods$utility_MergeInputWithOldBackportedPrefix.class)
+        .addClassTransformer(new ClassTransformer() {
+          @Override
+          public void visit(int version, int access, String name, String signature,
+              String superName, String[] interfaces) {
+            ClassAccessFlags accessFlags = ClassAccessFlags.fromCfAccessFlags(access);
+            accessFlags.setSynthetic();
+            super.visit(version, accessFlags.getAsCfAccessFlags(),
+                name, signature, superName, interfaces);
+          }
+        }).transform();
+
+    Path zip1 = temp.newFile("first.zip").toPath();
+    Path zip2 = temp.newFile("second.zip").toPath();
+    testForD8()
+        .setMinApi(AndroidApiLevel.L)
+        .addProgramClasses(MergeRunWithOldBackportedPrefix.class)
+        .addProgramClassFileData(transform)
+        .compile()
+        .assertNoMessages()
+        .writeToZip(zip1);
+    testForD8()
+        .setMinApi(AndroidApiLevel.L)
+        .addProgramClassFileData(transform)
+        .compile()
+        .assertNoMessages()
+        .writeToZip(zip2);
+    testForD8()
+        .addProgramFiles(zip1, zip2)
+        .setMinApi(AndroidApiLevel.L)
+        .compile()
+        .assertNoMessages()
+        .run(MergeRunWithOldBackportedPrefix.class)
+        .assertSuccessWithOutputLines("foobar");
+  }
+
+
+  static class $r8$java8methods$utility_MergeInputWithOldBackportedPrefix {
+    public void foo() {
+      System.out.println("foobar");
+    }
+
+  }
+
+  static class MergeRunWithOldBackportedPrefix {
+    public static void main(String[] args) {
+      $r8$java8methods$utility_MergeInputWithOldBackportedPrefix a =
+          new $r8$java8methods$utility_MergeInputWithOldBackportedPrefix();
+      a.foo();
+    }
+  }
+
   static class MergeInputA {
     public void foo() {
       System.out.println(Integer.hashCode(42));
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
new file mode 100644
index 0000000..2111b7d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
@@ -0,0 +1,34 @@
+// 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.desugar.graph;
+
+import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.origin.Origin;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class DesugarGraphTestConsumer implements DesugarGraphConsumer {
+
+  private Map<Origin, Set<Origin>> edges = new HashMap<>();
+
+  public boolean contains(Origin dependency, Origin dependent) {
+    return edges.getOrDefault(dependency, Collections.emptySet()).contains(dependent);
+  }
+
+  public int totalEdgeCount() {
+    int count = 0;
+    for (Set<Origin> dependents : edges.values()) {
+      count += dependents.size();
+    }
+    return count;
+  }
+
+  @Override
+  public synchronized void accept(Origin dependent, Origin dependency) {
+    edges.computeIfAbsent(dependency, s -> new HashSet<>()).add(dependent);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java
new file mode 100644
index 0000000..af3c6a0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java
@@ -0,0 +1,28 @@
+// 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.desugar.graph;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import java.io.IOException;
+
+public class DesugarGraphUtils {
+
+  public static Origin addClassWithOrigin(Class<?> clazz, D8TestBuilder builder)
+      throws IOException {
+    Origin origin = makeOrigin(clazz.getTypeName());
+    builder.getBuilder().addClassProgramData(ToolHelper.getClassAsBytes(clazz), origin);
+    return origin;
+  }
+
+  private static Origin makeOrigin(String name) {
+    return new Origin(Origin.root()) {
+      @Override
+      public String part() {
+        return name;
+      }
+    };
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java
new file mode 100644
index 0000000..ec8fcd4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceBridgeDependencyTest.java
@@ -0,0 +1,98 @@
+// 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.desugar.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InterfaceBridgeDependencyTest extends TestBase {
+
+  public interface I {
+    Object foo();
+  }
+
+  public interface J extends I {
+    // Covariant override of foo will result in a synthetic bridge method and J will depend on I.
+    default String foo() {
+      return "J::foo";
+    }
+  }
+
+  public interface K extends I {
+    // A simple override does not cause a bridge and there is no desugar dependencies for K.
+    default Object foo() {
+      return "K::foo";
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      // There are no instances of I, J or K, so just make sure that the code requires they exist.
+      System.out.println(I.class.getName());
+      System.out.println(J.class.getName());
+      System.out.println(K.class.getName());
+    }
+  }
+
+  // Test runner follows.
+
+  private static final String EXPECTED =
+      StringUtils.lines(I.class.getName(), J.class.getName(), K.class.getName());
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InterfaceBridgeDependencyTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClasses(I.class, J.class, K.class, TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+    } else {
+      D8TestBuilder builder = testForD8();
+      DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+      builder.getBuilder().setDesugarGraphConsumer(consumer);
+      Origin originI = DesugarGraphUtils.addClassWithOrigin(I.class, builder);
+      Origin originJ = DesugarGraphUtils.addClassWithOrigin(J.class, builder);
+      Origin originK = DesugarGraphUtils.addClassWithOrigin(K.class, builder);
+      builder
+          .setMinApi(parameters.getApiLevel())
+          .addProgramClasses(TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      // If API level indicates desugaring is needed check the edges are reported.
+      if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
+        assertTrue(consumer.contains(originI, originJ));
+        assertFalse(consumer.contains(originI, originK));
+        assertEquals(1, consumer.totalEdgeCount());
+      } else {
+        assertEquals(0, consumer.totalEdgeCount());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
index 703a237..7f76cad 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
@@ -4,18 +4,14 @@
 package com.android.tools.r8.desugar.graph;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.D8TestBuilder;
 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.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -26,7 +22,7 @@
 
   // The simplest program that gives rise to a dependency edge in the graph is an interface with
   // an implementing class. In this case, changes to the interface could require re-dexing the
-  // implementing class despite no derived changes happing to the classfile for the implementing
+  // implementing class despite no derived changes happening to the classfile for the implementing
   // class.
   //
   // For example, adding default method I.foo will trigger javac compilation of I and A to I.class
@@ -72,47 +68,24 @@
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutputLines("Hello World!");
     } else {
-      MyDesugarGraphConsumer consumer = new MyDesugarGraphConsumer();
-      Origin originI = makeOrigin("I");
-      Origin originA = makeOrigin("A");
-      testForD8()
-          .setMinApi(parameters.getApiLevel())
+      D8TestBuilder builder = testForD8();
+      DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+      builder.getBuilder().setDesugarGraphConsumer(consumer);
+      Origin originI = DesugarGraphUtils.addClassWithOrigin(I.class, builder);
+      Origin originA = DesugarGraphUtils.addClassWithOrigin(A.class, builder);
+      builder
           .addProgramClasses(TestClass.class)
-          .apply(
-              b ->
-                  b.getBuilder()
-                      .setDesugarGraphConsumer(consumer)
-                      .addClassProgramData(ToolHelper.getClassAsBytes(I.class), originI)
-                      .addClassProgramData(ToolHelper.getClassAsBytes(A.class), originA))
+          .setMinApi(parameters.getApiLevel())
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutputLines("Hello World!");
       // If API level indicates desugaring is needed check the edges are reported.
       if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
-        assertEquals(1, consumer.edges.size());
-        assertEquals(originI, consumer.edges.keySet().iterator().next());
-        assertEquals(originA, consumer.edges.values().iterator().next().iterator().next());
+        assertEquals(1, consumer.totalEdgeCount());
+        assertTrue(consumer.contains(originI, originA));
       } else {
-        assertEquals(0, consumer.edges.size());
+        assertEquals(0, consumer.totalEdgeCount());
       }
     }
   }
 
-  private Origin makeOrigin(String name) {
-    return new Origin(Origin.root()) {
-      @Override
-      public String part() {
-        return name;
-      }
-    };
-  }
-
-  public static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
-
-    Map<Origin, Set<Origin>> edges = new HashMap<>();
-
-    @Override
-    public synchronized void accept(Origin src, Origin dst) {
-      edges.computeIfAbsent(src, s -> new HashSet<>()).add(dst);
-    }
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index db68c25..289a0ba 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -170,7 +170,8 @@
     assertNull(appInfo.lookupDirectTarget(methodXOnTest));
 
     assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper));
-    assertNotNull(appInfo.lookupStaticTarget(methodXOnTest));
+    // Accessing a private target on a different type will fail resolution outright.
+    assertNull(appInfo.lookupStaticTarget(methodXOnTest));
 
     assertEquals("OK", runArt(application));
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
index 6ad7046..d7ecc12 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
@@ -4,9 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
@@ -15,9 +13,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Position;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
-import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Source;
 import com.android.tools.r8.utils.StringUtils;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -39,7 +35,6 @@
   }
 
   @Test
-  @Ignore("b/145985445")
   public void testParsingNoInlineSources() {
     String annotationData =
         StringUtils.join(
@@ -56,19 +51,16 @@
             "*E");
     Result result = KotlinSourceDebugExtensionParser.parse(annotationData);
     assertNotNull(result);
-    assertEquals(1, result.getFiles().size());
-    Source source = result.getFiles().get(1);
-    assertEquals("EnumSwitch.kt", source.getFileName());
-    assertEquals("enumswitch/EnumSwitchKt", source.getPath());
-    assertTrue(result.getPositions().containsKey(1));
-    Position position = result.getPositions().get(1);
-    assertEquals(source, position.getSource());
+    assertEquals(1, result.size());
+    assertEquals(1, (int) result.lookup(1).getKey());
+    Position position = result.lookup(1).getValue();
+    assertEquals("EnumSwitch.kt", position.getSource().getFileName());
+    assertEquals("enumswitch/EnumSwitchKt", position.getSource().getPath());
     assertEquals(1, position.getRange().from);
     assertEquals(38, position.getRange().to);
   }
 
   @Test
-  @Ignore("b/145985445")
   public void testParsingSimpleStrata() {
     // Taken from src/test/examplesKotlin/retrace/mainKt
     String annotationData =
@@ -100,32 +92,33 @@
             "*E");
     Result result = KotlinSourceDebugExtensionParser.parse(annotationData);
     assertNotNull(result);
-    assertEquals(3, result.getFiles().size());
+    assertEquals(3, result.size());
+    assertEquals(1, (int) result.lookup(1).getKey());
+    assertEquals(23, (int) result.lookup(23).getKey());
+    assertEquals(24, (int) result.lookup(24).getKey());
+
     // Check that files are correctly parsed.
-    Source source1 = result.getFiles().get(1);
-    assertEquals("Main.kt", source1.getFileName());
-    assertEquals("retrace/MainKt", source1.getPath());
+    Position pos1 = result.lookup(1).getValue();
+    assertEquals("Main.kt", pos1.getSource().getFileName());
+    assertEquals("retrace/MainKt", pos1.getSource().getPath());
 
-    Source source2 = result.getFiles().get(2);
-    assertEquals("InlineFunction.kt", source2.getFileName());
-    assertEquals("retrace/InlineFunctionKt", source2.getPath());
+    Position pos2 = result.lookup(23).getValue();
+    assertEquals("InlineFunction.kt", pos2.getSource().getFileName());
+    assertEquals("retrace/InlineFunctionKt", pos2.getSource().getPath());
 
-    Source source3 = result.getFiles().get(3);
-    assertEquals("InlineFunction.kt", source3.getFileName());
-    assertEquals("retrace/InlineFunction", source3.getPath());
+    Position pos3 = result.lookup(24).getValue();
+    assertEquals("InlineFunction.kt", pos3.getSource().getFileName());
+    assertEquals("retrace/InlineFunction", pos3.getSource().getPath());
 
     // Check that the inline positions can be traced.
-    assertTrue(result.getPositions().containsKey(23));
-    Position position1 = result.getPositions().get(23);
-    assertEquals(source2, position1.getSource());
-    assertEquals(7, position1.getRange().from);
-    assertEquals(7, position1.getRange().to);
+    assertEquals(1, pos1.getRange().from);
+    assertEquals(22, pos1.getRange().to);
 
-    assertTrue(result.getPositions().containsKey(24));
-    Position position2 = result.getPositions().get(24);
-    assertEquals(source3, position2.getSource());
-    assertEquals(12, position2.getRange().from);
-    assertEquals(12, position2.getRange().to);
+    assertEquals(7, pos2.getRange().from);
+    assertEquals(7, pos2.getRange().to);
+
+    assertEquals(12, pos3.getRange().from);
+    assertEquals(12, pos3.getRange().to);
   }
 
   @Test
@@ -286,12 +279,43 @@
             "7#2:23",
             "12#4:24", // <-- non-existing file index
             "*E");
+    assertNull(KotlinSourceDebugExtensionParser.parse(annotationData));
+  }
+
+  @Test
+  public void testRanges() {
+    String annotationData =
+        StringUtils.join(
+            "\n",
+            "SMAP",
+            "Main.kt",
+            "Kotlin",
+            "*S Kotlin",
+            "*F",
+            "+ 1 Main.kt",
+            "retrace/MainKt",
+            "+ 2 InlineFunction.kt",
+            "retrace/InlineFunctionKt",
+            "+ 3 InlineFunction.kt",
+            "retrace/InlineFunction",
+            "*L",
+            "1#1,22:1",
+            "7#2,1:23",
+            "12#3,2:24",
+            "*E",
+            "*S KotlinDebug",
+            "*F",
+            "+ 1 Main.kt",
+            "retrace/MainKt",
+            "*L",
+            "12#1:23",
+            "18#1:24",
+            "*E");
     Result parsedResult = KotlinSourceDebugExtensionParser.parse(annotationData);
     assertNotNull(parsedResult);
-
-    assertEquals(2, parsedResult.getPositions().size());
-    assertTrue(parsedResult.getPositions().containsKey(1));
-    assertTrue(parsedResult.getPositions().containsKey(23));
-    assertFalse(parsedResult.getPositions().containsKey(24));
+    assertEquals(24, (int) parsedResult.lookup(25).getKey());
+    Position value = parsedResult.lookup(25).getValue();
+    assertEquals(12, value.getRange().from);
+    assertEquals(13, value.getRange().to);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
index 375d4fa..837c7df 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
@@ -9,43 +9,40 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
-import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.naming.applymapping.shared.NoMappingDumps.HasMappingDump;
 import com.android.tools.r8.naming.applymapping.shared.NoMappingDumps.NoMappingDump;
 import com.android.tools.r8.naming.applymapping.shared.NoMappingDumps.NoMappingMainDump;
 import com.android.tools.r8.naming.applymapping.shared.SwappingDump.ADump;
 import com.android.tools.r8.naming.applymapping.shared.SwappingDump.BDump;
 import com.android.tools.r8.naming.applymapping.shared.SwappingDump.MainDump;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
+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.google.common.collect.ImmutableList;
-import java.nio.file.Path;
 import java.util.List;
 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 MemberResolutionAsmTest extends AsmTestBase {
-  private final Backend backend;
+public class MemberResolutionAsmTest extends TestBase {
+  private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public MemberResolutionAsmTest(Backend backend) {
-    this.backend = backend;
+  public MemberResolutionAsmTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   //  class HasMapping { // : X
@@ -74,47 +71,47 @@
   //      new NoMapping();
   //    }
   //  }
-  @Test
-  public void test_noMapping() throws Exception {
-    String main = "NoMappingMain";
-    AndroidApp input =
-        buildAndroidApp(HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+  private final String noMappingMain = "NoMappingMain";
+  private final String noMappingExpected = StringUtils.lines("HasMapping#foo", "NoMapping#foo");
 
-    Path mapPath = temp.newFile("test-mapping.txt").toPath();
-    List<String> pgMap =
-        ImmutableList.of(
+  private List<byte[]> noMappingInputs() throws Exception {
+    return ImmutableList.of(HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+  }
+
+  @Test
+  public void testNoMappingReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(noMappingInputs())
+        .run(parameters.getRuntime(), noMappingMain)
+        .assertSuccessWithOutput(noMappingExpected);
+  }
+
+  @Test
+  public void testNoMappingR8() throws Exception {
+    String pgMap =
+        StringUtils.joinLines(
+            "# Long comment to avoid reformatting of the lines below.",
             "HasMapping -> X:",
             "  void foo() -> a",
             "NoMapping -> Y:"
             // Intentionally missing a mapping for `private` foo().
             );
-    FileUtils.writeTextFile(mapPath, pgMap);
 
-    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
-    builder
-        .addProguardConfiguration(
-            ImmutableList.of(
-                keepMainProguardConfiguration(main),
-                // Do not turn on -allowaccessmodification
-                "-applymapping " + mapPath),
-            Origin.unknown())
-        .addLibraryFiles(runtimeJar(backend));
-    AndroidApp processedApp =
-        ToolHelper.runR8(
-            builder.build(),
-            options -> {
-              options.enableInlining = false;
-              options.enableVerticalClassMerging = false;
-            });
+    CodeInspector codeInspector =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(noMappingInputs())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(noMappingMain)
+            .addApplyMapping(pgMap)
+            .addOptionsModification(
+                options -> {
+                  options.enableInlining = false;
+                  options.enableVerticalClassMerging = false;
+                })
+            .run(parameters.getRuntime(), noMappingMain)
+            .assertSuccessWithOutput(noMappingExpected)
+            .inspector();
 
-    List<byte[]> classBytes =
-        ImmutableList.of(HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
-    ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
-    assertEquals(0, outputBefore.exitCode);
-    String outputAfter = runOnVM(processedApp, main, backend);
-    assertEquals(outputBefore.stdout.trim(), outputAfter.trim());
-
-    CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
     ClassSubject base = codeInspector.clazz("HasMapping");
     assertThat(base, isPresent());
     assertThat(base, isRenamed());
@@ -156,63 +153,64 @@
   //      new B().x(); // IllegalAccessError
   //    }
   //  }
-  @Test
-  public void test_swapping() throws Exception {
-    String main = "Main";
-    AndroidApp input = buildAndroidApp(ADump.dump(), BDump.dump(), MainDump.dump());
+  private final String swappingMain = "Main";
 
-    Path mapPath = temp.newFile("test-mapping.txt").toPath();
-    List<String> pgMap =
-        ImmutableList.of(
+  private List<byte[]> swappingInputs() throws Exception {
+    return ImmutableList.of(ADump.dump(), BDump.dump(), MainDump.dump());
+  }
+
+  private String getMethodSignature(String type, String method) {
+    if (parameters.isCfRuntime()) {
+      return type + "." + method + "()V";
+    }
+    assert parameters.isDexRuntime();
+    Version version = parameters.getRuntime().asDex().getVm().getVersion();
+    if (version.isOlderThanOrEqual(Version.V4_4_4)) {
+      return "L" + type + ";." + method + " ()V";
+    }
+    return "void " + type + "." + method + "()";
+  }
+
+  @Test
+  public void testSwappingReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(swappingInputs())
+        .run(parameters.getRuntime(), swappingMain)
+        .assertFailureWithErrorThatThrows(IllegalAccessError.class)
+        .assertFailureWithErrorThatMatches(containsString(getMethodSignature("A", "x")));
+  }
+
+  @Test
+  public void testSwappingR8() throws Exception {
+    String pgMap =
+        StringUtils.joinLines(
+            "# Long comment to avoid reformatting of the lines below.",
             "A -> X:",
             "  void x() -> y",
             "  void y() -> x",
             "B -> Y:"
             // Intentionally missing mappings for non-overridden members
             );
-    FileUtils.writeTextFile(mapPath, pgMap);
 
-    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
-    builder
-        .addProguardConfiguration(
-            ImmutableList.of(
-                keepMainProguardConfiguration(main),
-                // Do not turn on -allowaccessmodification
-                "-applymapping " + mapPath),
-            Origin.unknown())
-        .addLibraryFiles(runtimeJar(backend));
-    AndroidApp processedApp =
-        ToolHelper.runR8(
-            builder.build(),
-            options -> {
-              options.enableInlining = false;
-              options.enableVerticalClassMerging = false;
-            });
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClassFileData(swappingInputs())
+            .addKeepMainRule(swappingMain)
+            .addApplyMapping(pgMap)
+            .addOptionsModification(
+                options -> {
+                  options.enableInlining = false;
+                  options.enableVerticalClassMerging = false;
+                })
+            .compile();
 
-    List<byte[]> classBytes = ImmutableList.of(ADump.dump(), BDump.dump(), MainDump.dump());
-    ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
-    assertNotEquals(0, outputBefore.exitCode);
-    String expectedErrorMessage = "IllegalAccessError";
-    String expectedErrorSignature = "A.x()V";
-    assertThat(outputBefore.stderr, containsString(expectedErrorMessage));
-    assertThat(outputBefore.stderr, containsString(expectedErrorSignature));
-    ProcessResult outputAfter = runOnVMRaw(processedApp, main, backend);
-    assertNotEquals(0, outputAfter.exitCode);
-    expectedErrorSignature = "X.y()V";
-    if (backend == Backend.DEX) {
-      expectedErrorSignature = "void X.y()";
-      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
-        expectedErrorMessage = "IncompatibleClassChangeError";
-      }
-      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
-        expectedErrorMessage = "illegal method access";
-        expectedErrorSignature = "LX;.y ()V";
-      }
-    }
-    assertThat(outputAfter.stderr, containsString(expectedErrorMessage));
-    assertThat(outputAfter.stderr, containsString(expectedErrorSignature));
+    compileResult
+        .run(parameters.getRuntime(), swappingMain)
+        .assertFailureWithErrorThatThrows(IllegalAccessError.class)
+        .assertFailureWithErrorThatMatches(containsString(getMethodSignature("X", "y")));
 
-    CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+    CodeInspector codeInspector = compileResult.inspector();
     ClassSubject base = codeInspector.clazz("A");
     assertThat(base, isPresent());
     assertThat(base, isRenamed());
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index 5fe17d5..eb4cd97 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -4,23 +4,21 @@
 package com.android.tools.r8.resolution;
 
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
-import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -81,22 +79,9 @@
   }
 
   @Test
-  public void lookupSingleTarget() {
+  public void resolveTarget() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
-    assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
-    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
-    assertEquals(methodOnA, resolved.method);
-    DexEncodedMethod singleVirtualTarget =
-        appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
-    Assert.assertNull(singleVirtualTarget);
-  }
-
-  @Test
-  public void lookupVirtualTargets() {
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
-    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
-    assertEquals(methodOnA, resolved.method);
-    assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+    assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 22445e6..85ace0b 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -4,34 +4,29 @@
 package com.android.tools.r8.resolution;
 
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
-import org.junit.Assert;
 import org.junit.BeforeClass;
 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.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
-public class VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest extends AsmTestBase {
+public class VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest extends TestBase {
 
   public interface I {
     default void f() {}
@@ -64,51 +59,26 @@
 
   public static class BaseDump implements Opcodes {
 
-    static String prefix(String suffix) {
-      return VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.class
-              .getTypeName()
-              .replace('.', '/')
-          + suffix;
-    }
-
-    public static byte[] dump() {
-      ClassWriter cw = new ClassWriter(0);
-      MethodVisitor mv;
-      cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, prefix("$Base"), null, "java/lang/Object", null);
-      cw.visitSource("VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java", null);
-      {
-        mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-        mv.visitCode();
-        mv.visitVarInsn(ALOAD, 0);
-        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-        mv.visitInsn(RETURN);
-        mv.visitMaxs(1, 1);
-        mv.visitEnd();
-      }
-      {
-        // Changed ACC_PRIVATE to ACC_PUBLIC
-        mv = cw.visitMethod(ACC_PUBLIC, "f", "()V", null, null);
-        mv.visitCode();
-        mv.visitInsn(RETURN);
-        mv.visitMaxs(0, 1);
-        mv.visitEnd();
-      }
-      cw.visitEnd();
-      return cw.toByteArray();
+    public static byte[] dump() throws Exception {
+      return transformer(Base.class).setPublic(Base.class.getDeclaredMethod("f")).transform();
     }
   }
 
   public static List<Class<?>> CLASSES =
       ImmutableList.of(A.class, B.class, C.class, I.class, Main.class);
 
-  public static List<byte[]> DUMPS = ImmutableList.of(BaseDump.dump());
+  public static List<byte[]> getDumps() throws Exception {
+    return ImmutableList.of(BaseDump.dump());
+  }
 
   private static AppInfoWithLiveness appInfo;
 
   @BeforeClass
   public static void computeAppInfo() throws Exception {
     appInfo =
-        computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, DUMPS), Main.class).appInfo();
+        computeAppViewWithLiveness(
+                buildClasses(CLASSES).addClassProgramData(getDumps()).build(), Main.class)
+            .appInfo();
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
@@ -130,21 +100,9 @@
   }
 
   @Test
-  public void lookupSingleTarget() {
-    DexEncodedMethod resolved =
-        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB).getSingleTarget();
-    assertEquals(methodOnA, resolved.method);
-    DexEncodedMethod singleVirtualTarget =
-        appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
-    Assert.assertNull(singleVirtualTarget);
-  }
-
-  @Test
-  public void lookupVirtualTargets() {
+  public void testResolution() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
-    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
-    assertEquals(methodOnA, resolved.method);
-    assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+    assertTrue(resolutionResult.isFailedResolution());
   }
 
   @Test
@@ -154,13 +112,13 @@
       runResult =
           testForJvm()
               .addProgramClasses(CLASSES)
-              .addProgramClassFileData(DUMPS)
+              .addProgramClassFileData(getDumps())
               .run(parameters.getRuntime(), Main.class);
     } else {
       runResult =
           testForD8()
               .addProgramClasses(CLASSES)
-              .addProgramClassFileData(DUMPS)
+              .addProgramClassFileData(getDumps())
               .setMinApi(parameters.getApiLevel())
               .run(parameters.getRuntime(), Main.class);
     }
@@ -172,7 +130,7 @@
     R8TestRunResult runResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(CLASSES)
-            .addProgramClassFileData(DUMPS)
+            .addProgramClassFileData(getDumps())
             .addKeepMainRule(Main.class)
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), Main.class);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index 52dbd4b..8e21be0 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
@@ -121,6 +122,12 @@
     assertEquals(method.holder, declaredClassDefinition.type);
     ResolutionResult resolutionResult = appInfo.resolveMethod(declaredClassDefinition, method);
 
+    // Resolution fails when there is a mismatch between the symbolic reference and the definition.
+    if (!symbolicReferenceIsDefiningType) {
+      assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
+      return;
+    }
+
     // Verify that the resolved method is on the defining class.
     assertEquals(
         definingClassDefinition, resolutionResult.asSingleResolution().getResolvedHolder());
@@ -178,7 +185,7 @@
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransformedClasses())
         .run(parameters.getRuntime(), Main.class)
-        .apply(result -> checkExpectedResult(result, false));
+        .apply(this::checkExpectedResult);
   }
 
   @Test
@@ -189,10 +196,10 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .apply(result -> checkExpectedResult(result, true));
+        .apply(this::checkExpectedResult);
   }
 
-  private void checkExpectedResult(TestRunResult<?> result, boolean isR8) {
+  private void checkExpectedResult(TestRunResult<?> result) {
     // If not in the same nest, the error is always illegal access.
     if (!inSameNest) {
       result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
@@ -201,11 +208,6 @@
 
     // If in the same nest but the reference is not exact, the error is always no such method.
     if (!symbolicReferenceIsDefiningType) {
-      // TODO(b/145775365): R8 incorrectly compiles the input to a working program.
-      if (isR8) {
-        result.assertSuccessWithOutput(EXPECTED);
-        return;
-      }
       // TODO(b/145775365): D8/R8 does not preserve the thrown error.
       if (parameters.isDexRuntime()) {
         result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index bd6dbed..e3ca436 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -92,20 +92,11 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .apply(
-            result -> {
-              if (inSameNest) {
-                // TODO(b/145187969): R8 incorrectly compiles out the incorrect access.
-                result.assertSuccessWithOutput(EXPECTED);
-              } else {
-                checkExpectedResult(result);
-              }
-            });
+        .apply(this::checkExpectedResult);
   }
 
   private void checkExpectedResult(TestRunResult<?> result) {
     if (inSameNest && parameters.isCfRuntime()) {
-      // TODO(b/145187969): Investigate if the change to NoSuchMethodError is according to spec?
       result.assertFailureWithErrorThatMatches(containsString(NoSuchMethodError.class.getName()));
     } else {
       result.assertFailureWithErrorThatMatches(containsString(IllegalAccessError.class.getName()));
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index 3e85f1a..cdf2b22 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -91,23 +91,11 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .apply(
-            result -> {
-              if (inSameNest && parameters.isCfRuntime()) {
-                // TODO(b/145187969): R8/CF compiles to a "working" program.
-                result.assertSuccessWithOutput(EXPECTED);
-              } else if (inSameNest && parameters.isDexRuntime()) {
-                // TODO(b/145187969): R8/DEX compiles to a throw null program.
-                result.assertFailureWithErrorThatThrows(NullPointerException.class);
-              } else {
-                checkExpectedResult(result);
-              }
-            });
+        .apply(this::checkExpectedResult);
   }
 
   private void checkExpectedResult(TestRunResult<?> result) {
     if (inSameNest && parameters.isCfRuntime()) {
-      // TODO(b/145187969): Investigate if the change to NoSuchMethodError is according to spec?
       result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
     } else {
       result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
new file mode 100644
index 0000000..5d2a2d8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -0,0 +1,126 @@
+// 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.retrace;
+
+import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.containsInlinePosition;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+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 KotlinInlineFunctionInSameFileRetraceTests extends TestBase {
+
+  private static final String MAIN = "retrace.InlineFunctionsInSameFileKt";
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KotlinInlineFunctionInSameFileRetraceTests(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static Function<TestRuntime, Path> compilationResults =
+      memoizeFunction(KotlinInlineFunctionInSameFileRetraceTests::compileKotlinCode);
+
+  private static Path compileKotlinCode(TestRuntime runtime) throws IOException {
+    CfRuntime cfRuntime = runtime.isCf() ? runtime.asCf() : TestRuntime.getCheckedInJdk9();
+    return kotlinc(cfRuntime, getStaticTemp(), KOTLINC, KotlinTargetVersion.JAVA_8)
+        .addSourceFiles(
+            getFilesInTestFolderRelativeToClass(KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+        .compile();
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    testForRuntime(parameters)
+        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar()))
+        .run(parameters.getRuntime(), MAIN)
+        .assertFailureWithErrorThatMatches(containsString("foo"))
+        .assertFailureWithErrorThatMatches(
+            containsString(
+                "at retrace.InlineFunctionsInSameFileKt.main(InlineFunctionsInSameFile.kt:43"));
+  }
+
+  @Test
+  public void testRetraceKotlinInlineStaticFunction()
+      throws ExecutionException, CompilationFailedException, IOException {
+    Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+    CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
+    testForR8(parameters.getBackend())
+        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepAttributes("SourceFile", "LineNumberTable")
+        .setMode(CompilationMode.RELEASE)
+        .addKeepMainRule(MAIN)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertFailureWithErrorThatMatches(containsString("main"))
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(MAIN).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          kotlinInspector
+                              .clazz("retrace.InlineFunctionsInSameFileKt")
+                              .uniqueMethodWithName("foo")
+                              .asFoundMethodSubject(),
+                          1,
+                          8),
+                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 1, 43));
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+            });
+  }
+
+  private void checkInlineInformation(
+      StackTrace stackTrace,
+      CodeInspector codeInspector,
+      MethodSubject mainSubject,
+      InlinePosition inlineStack) {
+    assertThat(mainSubject, isPresent());
+    RetraceMethodResult retraceResult =
+        mainSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isThrow)
+            .collect(toSingle())
+            .retracePosition(codeInspector.retrace());
+    assertThat(retraceResult, isInlineFrame());
+    assertThat(retraceResult, isInlineStack(inlineStack));
+    assertThat(stackTrace, containsInlinePosition(inlineStack));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index d81c771..e2fe820 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -83,8 +83,6 @@
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
-        .addOptionsModification(
-            internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), main)
         .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
@@ -110,8 +108,6 @@
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
-        .addOptionsModification(
-            internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), main)
         .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
@@ -137,8 +133,6 @@
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
-        .addOptionsModification(
-            internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), main)
         .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
@@ -149,8 +143,8 @@
                   InlinePosition.stack(
                       InlinePosition.create(
                           "retrace.InlineFunctionKt", "inlineExceptionStatic", 3, 8),
-                      InlinePosition.create(
-                          "retrace.NestedInlineFunctionKt", "nestedInline", 3, 10),
+                      // TODO(b/146399675): There should be a nested frame on
+                      //  retrace.NestedInlineFunctionKt.nestedInline(line 10).
                       InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
@@ -166,8 +160,6 @@
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
-        .addOptionsModification(
-            internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), main)
         .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
@@ -178,8 +170,8 @@
                   InlinePosition.stack(
                       InlinePosition.create(
                           "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
-                      InlinePosition.create(
-                          "retrace.NestedInlineFunctionKt", "nestedInlineOnFirstLine", 2, 15),
+                      // TODO(b/146399675): There should be a nested frame on
+                      //  retrace.NestedInlineFunctionKt.nestedInlineOnFirstLine(line 15).
                       InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
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 77d0846..59a67ad 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.retrace.Retrace.RetraceAbortException;
 import com.android.tools.r8.retrace.stacktraces.ActualBotStackTraceBase;
 import com.android.tools.r8.retrace.stacktraces.ActualIdentityStackTrace;
@@ -29,7 +28,9 @@
 import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
 import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,12 +40,21 @@
 @RunWith(Parameterized.class)
 public class RetraceTests extends TestBase {
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  // This is a slight modification of the default regular expression shown for proguard retrace
+  // that allow for retracing classes in the form <class>: lorem ipsum...
+  // Seems like Proguard retrace is expecting the form "Caused by: <class>".
+  private static final String DEFAULT_REGULAR_EXPRESSION =
+      "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)|(?:(?:(?:%c|.*)?[:\"]\\s+)?%c(?::.*)?)";
+
+  @Parameters(name = "{0}, use regular expression: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
   }
 
-  public RetraceTests(TestParameters parameters) {
+  private final boolean useRegExpParsing;
+
+  public RetraceTests(TestParameters parameters, boolean useRegExpParsing) {
+    this.useRegExpParsing = useRegExpParsing;
   }
 
   @Test
@@ -93,11 +103,15 @@
   public void testInvalidStackTraceLineWarnings() {
     InvalidStackTrace invalidStackTraceTest = new InvalidStackTrace();
     TestDiagnosticMessagesImpl diagnosticsHandler = runRetraceTest(invalidStackTraceTest);
-    diagnosticsHandler.assertOnlyWarnings();
-    diagnosticsHandler.assertWarningsCount(invalidStackTraceTest.expectedWarnings());
-    assertThat(
-        diagnosticsHandler.getWarnings().get(0).getDiagnosticMessage(),
-        containsString(". . . 7 more"));
+    if (!useRegExpParsing) {
+      diagnosticsHandler.assertOnlyWarnings();
+      diagnosticsHandler.assertWarningsCount(invalidStackTraceTest.expectedWarnings());
+      assertThat(
+          diagnosticsHandler.getWarnings().get(0).getDiagnosticMessage(),
+          containsString(". . . 7 more"));
+    } else {
+      diagnosticsHandler.assertNoMessages();
+    }
   }
 
   @Test
@@ -110,7 +124,8 @@
     List<ActualBotStackTraceBase> stackTraces =
         ImmutableList.of(new ActualIdentityStackTrace(), new ActualRetraceBotStackTrace());
     for (ActualBotStackTraceBase stackTrace : stackTraces) {
-      runRetraceTest(stackTrace).assertWarningsCount(stackTrace.expectedWarnings());
+      runRetraceTest(stackTrace)
+          .assertWarningsCount(useRegExpParsing ? 0 : stackTrace.expectedWarnings());
     }
   }
 
@@ -140,6 +155,7 @@
         RetraceCommand.builder(diagnosticsHandler)
             .setProguardMapProducer(stackTraceForTest::mapping)
             .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
+            .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
             .setRetracedStackTraceConsumer(
                 retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
             .build();
diff --git a/src/test/java/com/android/tools/r8/retrace/kt/InlineFunctionsInSameFile.kt b/src/test/java/com/android/tools/r8/retrace/kt/InlineFunctionsInSameFile.kt
new file mode 100644
index 0000000..2047a48
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/kt/InlineFunctionsInSameFile.kt
@@ -0,0 +1,22 @@
+// 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 retrace
+
+inline fun foo() {
+  bar {
+     throw Exception("foo")
+  }
+}
+
+inline fun bar(f: () -> Unit) {
+  baz { f() }
+}
+
+inline fun baz(f: () -> Unit) {
+  f()
+}
+
+fun main(args: Array<String>) {
+  foo()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
index 62aa5ac..b44b364 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
@@ -27,13 +27,13 @@
     return Arrays.asList(
         "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java:7)",
-        "    or                         foo(R8.java:7)",
+        "    <OR> at com.android.tools.r8.R8.foo(R8.java:7)",
         "    at com.android.tools.r8.R8.bar(R8.java:8)",
-        "    or                         foo(R8.java:8)",
+        "    <OR> at com.android.tools.r8.R8.foo(R8.java:8)",
         "    at com.android.tools.r8.R8.main(R8.java)",
         "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java:9)",
-        "    or                         foo(R8.java:9)",
+        "    <OR> at com.android.tools.r8.R8.foo(R8.java:9)",
         "    ... 42 more");
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
index 76b15e0..a3a8e58 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
@@ -54,13 +54,13 @@
     return Arrays.asList(
         "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
-        "    or                         foo(R8.java)",
+        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
-        "    or                         foo(R8.java)",
+        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
         "    at com.android.tools.r8.R8.main(R8.java)",
         "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
-        "    or                         foo(R8.java)",
+        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
         "    ... 42 more");
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
index af84f69..ce9f1c5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.rewrite.assertions.testclasses.TestClassForInnerClass;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -25,12 +26,35 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
 
 @RunWith(Parameterized.class)
-public class AssertionsConfigurationTest extends TestBase {
+public class AssertionsConfigurationTest extends TestBase implements Opcodes {
 
   private final TestParameters parameters;
 
+  private Class<?> class1 = com.android.tools.r8.rewrite.assertions.testclasses.Class1.class;
+  private Class<?> class2 = com.android.tools.r8.rewrite.assertions.testclasses.Class2.class;
+  private Class<?> subpackageClass1 =
+      com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class;
+  private Class<?> subpackageClass2 =
+      com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class;
+
+  private List<Class<?>> testClasses =
+      ImmutableList.of(TestClass.class, class1, class2, subpackageClass1, subpackageClass2);
+
+  private String packageName =
+      com.android.tools.r8.rewrite.assertions.testclasses.Class1.class.getPackage().getName();
+  private String subPackageName =
+      com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class
+          .getPackage()
+          .getName();
+
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevels().build();
@@ -47,12 +71,7 @@
       List<String> outputLines)
       throws Exception {
     testForD8()
-        .addProgramClasses(
-            TestClass.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+        .addProgramClasses(testClasses)
         .setMinApi(parameters.getApiLevel())
         .addAssertionsConfiguration(assertionsConfigurationBuilder)
         .compile()
@@ -79,18 +98,9 @@
       throws Exception {
 
     testForR8(parameters.getBackend())
-        .addProgramClasses(
-            TestClass.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+        .addProgramClasses(testClasses)
         .addKeepMainRule(TestClass.class)
-        .addKeepClassAndMembersRules(
-            com.android.tools.r8.rewrite.assertions.testclasses.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.Class2.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class,
-            com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class)
+        .addKeepClassAndMembersRules(class1, class2, subpackageClass1, subpackageClass2)
         .setMinApi(parameters.getApiLevel())
         .addAssertionsConfiguration(assertionsConfigurationBuilder)
         .compile()
@@ -125,8 +135,7 @@
     return ImmutableList.of("DONE");
   }
 
-  private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) {
-    ClassSubject subject = inspector.clazz(clazz);
+  private void checkAssertionCodeRemoved(ClassSubject subject) {
     assertThat(subject, isPresent());
     // <clinit> is removed by R8 as it becomes empty.
     if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
@@ -143,8 +152,11 @@
             .anyMatch(InstructionSubject::isThrow));
   }
 
-  private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
-    ClassSubject subject = inspector.clazz(clazz);
+  private void checkAssertionCodeRemoved(CodeInspector inspector, Class<?> clazz) {
+    checkAssertionCodeRemoved(inspector.clazz(clazz));
+  }
+
+  private void checkAssertionCodeEnabled(ClassSubject subject) {
     assertThat(subject, isPresent());
     // <clinit> is removed by R8.
     if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
@@ -161,6 +173,10 @@
             .anyMatch(InstructionSubject::isThrow));
   }
 
+  private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
+    checkAssertionCodeEnabled(inspector.clazz(clazz));
+  }
+
   private void checkAssertionCodeLeft(CodeInspector inspector, Class<?> clazz) {
     ClassSubject subject = inspector.clazz(clazz);
     assertThat(subject, isPresent());
@@ -177,40 +193,28 @@
   }
 
   private void checkAssertionCodeRemoved(CodeInspector inspector) {
-    checkAssertionCodeRemoved(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
-    checkAssertionCodeRemoved(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
-    checkAssertionCodeRemoved(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
-    checkAssertionCodeRemoved(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+    checkAssertionCodeRemoved(inspector, class1);
+    checkAssertionCodeRemoved(inspector, class2);
+    checkAssertionCodeRemoved(inspector, subpackageClass1);
+    checkAssertionCodeRemoved(inspector, subpackageClass2);
   }
 
   private void checkAssertionCodeEnabled(CodeInspector inspector) {
-    checkAssertionCodeEnabled(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
-    checkAssertionCodeEnabled(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
-    checkAssertionCodeEnabled(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
-    checkAssertionCodeEnabled(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+    checkAssertionCodeEnabled(inspector, class1);
+    checkAssertionCodeEnabled(inspector, class2);
+    checkAssertionCodeEnabled(inspector, subpackageClass1);
+    checkAssertionCodeEnabled(inspector, subpackageClass2);
   }
 
   private void checkAssertionCodeLeft(CodeInspector inspector) {
-    checkAssertionCodeLeft(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class1.class);
-    checkAssertionCodeLeft(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.Class2.class);
-    checkAssertionCodeLeft(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class1.class);
-    checkAssertionCodeLeft(
-        inspector, com.android.tools.r8.rewrite.assertions.testclasses.subpackage.Class2.class);
+    checkAssertionCodeLeft(inspector, class1);
+    checkAssertionCodeLeft(inspector, class2);
+    checkAssertionCodeLeft(inspector, subpackageClass1);
+    checkAssertionCodeLeft(inspector, subpackageClass2);
   }
 
   @Test
-  public void testEnableAllAssertionsForDex() throws Exception {
+  public void testAssertionsForDex() throws Exception {
     Assume.assumeTrue(parameters.isDexRuntime());
     // Leaving assertions in or disabling them on Dalvik/Art means no assertions.
     runD8Test(
@@ -230,6 +234,15 @@
         this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
     runR8Test(
         this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
+    // Enabling for the package should enable all.
+    runD8Test(
+        builder -> builder.enableForPackage(packageName).build(),
+        this::checkAssertionCodeEnabled,
+        allAssertionsExpectedLines());
+    runR8Test(
+        builder -> builder.enableForPackage(packageName).build(),
+        this::checkAssertionCodeEnabled,
+        allAssertionsExpectedLines());
   }
 
   @Test
@@ -259,6 +272,415 @@
         true);
   }
 
+  @Test
+  public void testEnableForPackageForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    runD8Test(
+        builder -> builder.enableForPackage(subPackageName).build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, subpackageClass1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.subpackage.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+    runR8Test(
+        builder -> builder.enableForPackage(subPackageName).build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, subpackageClass1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.subpackage.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+  }
+
+  @Test
+  public void testEnableForClassForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    runD8Test(
+        builder ->
+            builder
+                .enableForClass(class1.getCanonicalName())
+                .enableForClass(subpackageClass2.getCanonicalName())
+                .build(),
+        inspector -> {
+          // checkAssertionCodeEnabled(inspector, class1);
+          // checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+    runR8Test(
+        builder ->
+            builder
+                .enableForClass(class1.getCanonicalName())
+                .enableForClass(subpackageClass2.getCanonicalName())
+                .build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, class1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+  }
+
+  @Test
+  public void testMixedForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    runD8Test(
+        builder ->
+            builder
+                .enableForPackage(packageName)
+                .disableForClass(class2.getCanonicalName())
+                .disableForClass(subpackageClass1.getCanonicalName())
+                .build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, class1);
+          checkAssertionCodeRemoved(inspector, class2);
+          checkAssertionCodeRemoved(inspector, subpackageClass1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+    runR8Test(
+        builder ->
+            builder
+                .enableForPackage(packageName)
+                .disableForClass(class2.getCanonicalName())
+                .disableForClass(subpackageClass1.getCanonicalName())
+                .build(),
+        inspector -> {
+          checkAssertionCodeEnabled(inspector, class1);
+          checkAssertionCodeRemoved(inspector, class2);
+          checkAssertionCodeRemoved(inspector, subpackageClass1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
+        },
+        ImmutableList.of(
+            "AssertionError in testclasses.Class1",
+            "AssertionError in testclasses.subpackage.Class2",
+            "DONE"));
+  }
+
+  @Test
+  public void testUnnamedPackageForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(class1, class2)
+        .addProgramClassFileData(
+            testClassForUnknownPackage(),
+            classInUnnamedPackage("Class1"),
+            classInUnnamedPackage("Class2"))
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(builder -> builder.enableForPackage("").build())
+        .compile()
+        .inspect(
+            inspector -> {
+              checkAssertionCodeEnabled(inspector.clazz("Class1"));
+              checkAssertionCodeEnabled(inspector.clazz("Class2"));
+              checkAssertionCodeRemoved(inspector.clazz(class1));
+              checkAssertionCodeRemoved(inspector.clazz(class2));
+            })
+        .run(parameters.getRuntime(), "Main")
+        .assertSuccessWithOutputLines(
+            "AssertionError in Class1", "AssertionError in Class2", "DONE");
+  }
+
+  @Test
+  public void testInnerClassForJvm() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+    // Pointing to the outer class enables assertions for the inner as well.
+    testForJvm()
+        .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+        .addVmArguments("-ea:" + TestClassForInnerClass.class.getCanonicalName())
+        .run(parameters.getRuntime(), TestClassForInnerClass.class)
+        .assertSuccessWithOutputLines(
+            "AssertionError in TestClassForInnerClass",
+            "AssertionError in TestClassForInnerClass.InnerClass",
+            "DONE");
+
+    // Pointing to the inner class enables no assertions.
+    testForJvm()
+        .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+        .addVmArguments("-ea:" + TestClassForInnerClass.InnerClass.class.getCanonicalName())
+        .run(parameters.getRuntime(), TestClassForInnerClass.class)
+        .assertSuccessWithOutputLines("DONE");
+    testForJvm()
+        .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+        .addVmArguments("-ea:" + TestClassForInnerClass.InnerClass.class.getTypeName())
+        .run(parameters.getRuntime(), TestClassForInnerClass.class)
+        .assertSuccessWithOutputLines("DONE");
+  }
+
+  @Test
+  public void testInnerClassForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(TestClassForInnerClass.class, TestClassForInnerClass.InnerClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(
+            builder ->
+                builder.enableForClass(TestClassForInnerClass.class.getCanonicalName()).build())
+        .compile()
+        .inspect(
+            inspector -> {
+              checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.class));
+              checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.InnerClass.class));
+            })
+        .run(parameters.getRuntime(), TestClassForInnerClass.class)
+        .assertSuccessWithOutputLines(
+            "AssertionError in TestClassForInnerClass",
+            "AssertionError in TestClassForInnerClass.InnerClass",
+            "DONE");
+  }
+  /**
+   * Code for the following class in the unnamed package:
+   *
+   * <p>public class Main { public static void main(String[] args) { try { Class1.m(); } catch
+   * (AssertionError e) { System.out.println("AssertionError in Class1"); } try { Class2.m(); }
+   * catch (AssertionError e) { System.out.println("AssertionError in Class2"); } try {
+   * com.android.tools.r8.rewrite.assertions.Class1.m(); } catch (AssertionError e) {
+   * System.out.println("AssertionError in Class1"); } try {
+   * com.android.tools.r8.rewrite.assertions.Class2.m(); } catch (AssertionError e) {
+   * System.out.println("AssertionError in Class2"); } System.out.println("DONE"); } }
+   */
+  public static byte[] testClassForUnknownPackage() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_FINAL | ACC_SUPER, "Main", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Main.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(1, 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, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      Label label1 = new Label();
+      Label label2 = new Label();
+      methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/AssertionError");
+      Label label3 = new Label();
+      Label label4 = new Label();
+      Label label5 = new Label();
+      methodVisitor.visitTryCatchBlock(label3, label4, label5, "java/lang/AssertionError");
+      Label label6 = new Label();
+      Label label7 = new Label();
+      Label label8 = new Label();
+      methodVisitor.visitTryCatchBlock(label6, label7, label8, "java/lang/AssertionError");
+      Label label9 = new Label();
+      Label label10 = new Label();
+      Label label11 = new Label();
+      methodVisitor.visitTryCatchBlock(label9, label10, label11, "java/lang/AssertionError");
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(4, label0);
+      methodVisitor.visitMethodInsn(INVOKESTATIC, "Class1", "m", "()V", false);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(7, label1);
+      methodVisitor.visitJumpInsn(GOTO, label3);
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(5, label2);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label12 = new Label();
+      methodVisitor.visitLabel(label12);
+      methodVisitor.visitLineNumber(6, label12);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("AssertionError in Class1");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(9, label3);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitMethodInsn(INVOKESTATIC, "Class2", "m", "()V", false);
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(12, label4);
+      methodVisitor.visitJumpInsn(GOTO, label6);
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(10, label5);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label13 = new Label();
+      methodVisitor.visitLabel(label13);
+      methodVisitor.visitLineNumber(11, label13);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("AssertionError in Class2");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitLineNumber(14, label6);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/rewrite/assertions/testclasses/Class1",
+          "m",
+          "()V",
+          false);
+      methodVisitor.visitLabel(label7);
+      methodVisitor.visitLineNumber(17, label7);
+      methodVisitor.visitJumpInsn(GOTO, label9);
+      methodVisitor.visitLabel(label8);
+      methodVisitor.visitLineNumber(15, label8);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label14 = new Label();
+      methodVisitor.visitLabel(label14);
+      methodVisitor.visitLineNumber(16, label14);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("AssertionError in testclasses.Class1");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitLabel(label9);
+      methodVisitor.visitLineNumber(19, label9);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/rewrite/assertions/testclasses/Class2",
+          "m",
+          "()V",
+          false);
+      methodVisitor.visitLabel(label10);
+      methodVisitor.visitLineNumber(22, label10);
+      Label label15 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label15);
+      methodVisitor.visitLabel(label11);
+      methodVisitor.visitLineNumber(20, label11);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/AssertionError"});
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label16 = new Label();
+      methodVisitor.visitLabel(label16);
+      methodVisitor.visitLineNumber(21, label16);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("AssertionError in testclasses.Class2");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitLabel(label15);
+      methodVisitor.visitLineNumber(24, label15);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("DONE");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label17 = new Label();
+      methodVisitor.visitLabel(label17);
+      methodVisitor.visitLineNumber(25, label17);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 2);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
+  /**
+   * Code for the following class in the unnamed package:
+   *
+   * <p>public class <name> { public static void m() { assert false; } }
+   */
+  public static byte[] classInUnnamedPackage(String name) {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, name, null, "java/lang/Object", null);
+
+    classWriter.visitSource(name + ".java", null);
+
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC, "$assertionsDisabled", "Z", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(1, 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, "m", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(3, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, name, "$assertionsDisabled", "Z");
+      Label label1 = new Label();
+      methodVisitor.visitJumpInsn(IFNE, label1);
+      methodVisitor.visitTypeInsn(NEW, "java/lang/AssertionError");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/AssertionError", "<init>", "()V", false);
+      methodVisitor.visitInsn(ATHROW);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(4, label1);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 0);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(1, label0);
+      methodVisitor.visitLdcInsn(Type.getType("L" + name + ";"));
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
+      Label label1 = new Label();
+      methodVisitor.visitJumpInsn(IFNE, label1);
+      methodVisitor.visitInsn(ICONST_1);
+      Label label2 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label2);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitInsn(ICONST_0);
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
+      methodVisitor.visitFieldInsn(PUTSTATIC, name, "$assertionsDisabled", "Z");
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
   static class TestClass {
     public static void main(String[] args) {
       try {
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 446821a..27e53ba 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -199,7 +199,7 @@
         .debug()
         .noTreeShaking()
         .noMinification()
-        .addOptionsModification(o -> o.assertionTransformation = transformation)
+        .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
         .compile();
   }
 
@@ -337,7 +337,7 @@
         .addProgramClasses(ClassWithAssertions.class)
         .debug()
         .setMinApi(AndroidApiLevel.B)
-        .addOptionsModification(o -> o.assertionTransformation = transformation)
+        .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
         .compile();
   }
 
@@ -357,7 +357,7 @@
         .addProgramFiles(program)
         .debug()
         .setMinApi(AndroidApiLevel.B)
-        .addOptionsModification(o -> o.assertionTransformation = transformation)
+        .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
         .compile();
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java
new file mode 100644
index 0000000..6a719f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/TestClassForInnerClass.java
@@ -0,0 +1,31 @@
+// 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.rewrite.assertions.testclasses;
+
+public class TestClassForInnerClass {
+  public static class InnerClass {
+    public static void m() {
+      assert false;
+    }
+  }
+
+  public static void m() {
+    assert false;
+  }
+
+  public static void main(String[] args) {
+    try {
+      m();
+    } catch (AssertionError e) {
+      System.out.println("AssertionError in TestClassForInnerClass");
+    }
+    try {
+      InnerClass.m();
+    } catch (AssertionError e) {
+      System.out.println("AssertionError in TestClassForInnerClass.InnerClass");
+    }
+    System.out.println("DONE");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/testing/JvmVmArgumentsTest.java b/src/test/java/com/android/tools/r8/testing/JvmVmArgumentsTest.java
new file mode 100644
index 0000000..8fdc339
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testing/JvmVmArgumentsTest.java
@@ -0,0 +1,76 @@
+// 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.testing;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class JvmVmArgumentsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public JvmVmArgumentsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testArguments() throws Exception {
+    testForJvm()
+        .addTestClasspath()
+        .addVmArguments("-ea")
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("AssertionError!", "DONE");
+  }
+
+  @Test
+  public void testMultipleArguments() throws Exception {
+    testForJvm()
+        .addTestClasspath()
+        .addVmArguments("-ea", "-da")
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("DONE");
+
+    testForJvm()
+        .addTestClasspath()
+        .addVmArguments("-ea")
+        .addVmArguments("-da")
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("DONE");
+  }
+
+  @Test
+  public void testNoArguments() throws Exception {
+    testForJvm()
+        .addTestClasspath()
+        .addVmArguments()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("DONE");
+  }
+
+  static class TestClass {
+    public static void m() {
+      assert false;
+    }
+
+    public static void main(String[] args) {
+      try {
+        m();
+      } catch (AssertionError e) {
+        System.out.println("AssertionError!");
+      }
+      System.out.println("DONE");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 461323d..3d653d0 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -20,6 +20,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
@@ -238,7 +239,27 @@
             });
   }
 
+  public ClassFileTransformer setPublic(Method method) {
+    return setAccessFlags(
+        method,
+        accessFlags -> {
+          accessFlags.unsetPrivate();
+          accessFlags.unsetProtected();
+          accessFlags.setPublic();
+        });
+  }
+
   public ClassFileTransformer setPrivate(Method method) {
+    return setAccessFlags(
+        method,
+        accessFlags -> {
+          accessFlags.unsetPublic();
+          accessFlags.unsetProtected();
+          accessFlags.setPrivate();
+        });
+  }
+
+  public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
     return addClassTransformer(
         new ClassTransformer() {
           final MethodReference methodReference = Reference.methodFromMethod(method);
@@ -253,9 +274,7 @@
                 MethodAccessFlags.fromCfAccessFlags(access, isConstructor);
             if (name.equals(methodReference.getMethodName())
                 && descriptor.equals(methodReference.getMethodDescriptor())) {
-              accessFlags.unsetPublic();
-              accessFlags.unsetProtected();
-              accessFlags.setPrivate();
+              setter.accept(accessFlags);
             }
             return super.visitMethod(
                 accessFlags.getAsCfAccessFlags(), name, descriptor, signature, exceptions);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index 358a016..e4ce45d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -3,11 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
@@ -53,6 +54,7 @@
     return false;
   }
 
+  // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType
   private String getDescriptorFromKmType(KmType kmType) {
     if (kmType == null) {
       return null;
@@ -61,7 +63,12 @@
     kmType.accept(new KmTypeVisitor() {
       @Override
       public void visitClass(String name) {
-        descriptor.set(DescriptorUtils.getDescriptorFromKotlinClassifier(name));
+        descriptor.set(getDescriptorFromKotlinClassifier(name));
+      }
+
+      @Override
+      public void visitTypeAlias(String name) {
+        descriptor.set(getDescriptorFromKotlinClassifier(name));
       }
     });
     return descriptor.get();