Merge commit '537d711fb733af80445dcf523dff772ddd844c3d' 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 15acd6f..7199b77 100644
--- a/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
+++ b/src/main/java/com/android/tools/r8/AssertionsConfiguration.java
@@ -4,8 +4,7 @@
 
 package com.android.tools.r8;
 
-import java.util.ArrayList;
-import java.util.List;
+import com.android.tools.r8.utils.Reporter;
 
 @Keep
 public class AssertionsConfiguration {
@@ -24,55 +23,37 @@
     PASSTHROUGH
   }
 
-  public enum ConfigurationType {
+  public enum AssertionTransformationScope {
     ALL,
     PACKAGE,
     CLASS
   }
 
-  public static class ConfigurationEntry {
-    private final AssertionTransformation transformation;
-    private final ConfigurationType type;
-    private final String value;
+  private final AssertionTransformation transformation;
+  private final AssertionTransformationScope scope;
+  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;
-    }
+  AssertionsConfiguration(
+      AssertionTransformation transformation, AssertionTransformationScope scope, String value) {
+    this.transformation = transformation;
+    this.scope = scope;
+    this.value = value;
   }
 
-  // Methods which need to be public.
-  public static class InternalAssertionConfiguration {
-
-    public static List<ConfigurationEntry> getConfiguration(AssertionsConfiguration configuration) {
-      return configuration.entries;
-    }
+  public AssertionTransformation getTransformation() {
+    return transformation;
   }
 
-  private final List<ConfigurationEntry> entries;
-
-  private AssertionsConfiguration(List<ConfigurationEntry> entries) {
-    this.entries = entries;
+  public AssertionTransformationScope getScope() {
+    return scope;
   }
 
-  static AssertionsConfiguration.Builder builder(AssertionsConfiguration previous) {
-    return new AssertionsConfiguration.Builder(
-        previous != null ? previous.entries : new ArrayList<>());
+  public String getValue() {
+    return value;
+  }
+
+  static AssertionsConfiguration.Builder builder(Reporter reporter) {
+    return new AssertionsConfiguration.Builder(reporter);
   }
 
   /**
@@ -83,33 +64,19 @@
    */
   @Keep
   public static class Builder {
-    private final List<ConfigurationEntry> entries;
+    Reporter reporter;
+    private AssertionTransformation transformation;
+    private AssertionTransformationScope scope;
+    private String value;
 
-    private Builder(List<ConfigurationEntry> previousEntries) {
-      assert previousEntries != null;
-      this.entries = previousEntries;
-    }
-
-    private void addEntry(
-        AssertionTransformation transformation, ConfigurationType type, String value) {
-      entries.add(new ConfigurationEntry(transformation, type, value));
+    private Builder(Reporter reporter) {
+      this.reporter = reporter;
     }
 
     /** Set how to handle javac generated assertion code. */
     public AssertionsConfiguration.Builder setTransformation(
         AssertionTransformation 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);
-      }
+      this.transformation = transformation;
       return this;
     }
 
@@ -117,7 +84,7 @@
      * Unconditionally enable javac generated assertion code in all packages and classes. This
      * corresponds to passing <code>-enableassertions</code> or <code>-ea</code> to the java CLI.
      */
-    public AssertionsConfiguration.Builder enable() {
+    public AssertionsConfiguration.Builder setEnable() {
       setTransformation(AssertionTransformation.ENABLE);
       return this;
     }
@@ -126,86 +93,145 @@
      * Disable the javac generated assertion code in all packages and classes. This corresponds to
      * passing <code>-disableassertions</code> or <code>-da</code> to the java CLI.
      */
-    public AssertionsConfiguration.Builder disable() {
+    public AssertionsConfiguration.Builder setDisable() {
       setTransformation(AssertionTransformation.DISABLE);
       return this;
     }
 
     /** Passthrough of the javac generated assertion code in all packages and classes. */
-    public AssertionsConfiguration.Builder passthrough() {
+    public AssertionsConfiguration.Builder setPassthrough() {
       setTransformation(AssertionTransformation.PASSTHROUGH);
       return this;
     }
 
-    /** Set how to handle javac generated assertion code in package and all subpackages. */
-    public AssertionsConfiguration.Builder setTransformationForPackage(
-        String packageName, AssertionTransformation transformation) {
-      addEntry(transformation, ConfigurationType.PACKAGE, packageName);
+    public AssertionsConfiguration.Builder setScopeAll() {
+      this.scope = AssertionTransformationScope.ALL;
+      this.value = null;
       return this;
     }
 
     /**
-     * Unconditionally enable javac generated assertion code in package <code>packageName</code> and
-     * all subpackages. This corresponds to passing <code>-enableassertions:packageName...</code> or
-     * <code>-ea:packageName...</code> to the java CLI.
+     * Apply the specified transformation in package <code>packageName</code> and all subpackages.
+     * If <code>packageName</code> is the empty string, this specifies that the transformation is
+     * applied ion the unnamed package.
      *
-     * <p>If <code>packageName</code> is the empty string, assertions are enabled in the unnamed
-     * package, which corresponds to passing <code>-enableassertions:...</code> or <code>-ea:...
-     * </code> to the java CLI.
-     */
-    public AssertionsConfiguration.Builder enableForPackage(String packageName) {
-      return setTransformationForPackage(packageName, AssertionTransformation.ENABLE);
-    }
-
-    /**
-     * Disable the javac generated assertion code in package <code>packageName</code> and all
-     * subpackages. This corresponds to passing <code>-disableassertions:packageName...</code> or
-     * <code>-da:packageName...</code> to the java CLI.
+     * <p>If the transformation is 'enable' this corresponds to passing <code>
+     * -enableassertions:packageName...</code> or <code>-ea:packageName...</code> to the java CLI.
      *
-     * <p>If <code>packageName</code> is the empty string assertions are disabled in the unnamed
-     * package, which corresponds to passing <code>-disableassertions:...</code> or <code>-da:...
-     * </code> to the java CLI.
+     * <p>If the transformation is 'disable' this corresponds to passing <code>
+     * -disableassertions:packageName...</code> or <code>-da:packageName...</code> to the java CLI.
      */
-    public AssertionsConfiguration.Builder disableForPackage(String packageName) {
-      return setTransformationForPackage(packageName, AssertionTransformation.DISABLE);
-    }
-
-    public AssertionsConfiguration.Builder passthroughForPackage(String packageName) {
-      return setTransformationForPackage(packageName, AssertionTransformation.PASSTHROUGH);
-    }
-
-    /** Set how to handle javac generated assertion code in class. */
-    public AssertionsConfiguration.Builder setTransformationForClass(
-        String className, AssertionTransformation transformation) {
-      addEntry(transformation, ConfigurationType.CLASS, className);
+    public AssertionsConfiguration.Builder setScopePackage(String packageName) {
+      this.scope = AssertionTransformationScope.PACKAGE;
+      this.value = packageName;
       return this;
     }
 
     /**
-     * Unconditionally enable javac generated assertion in class <code>className</code>. This
-     * corresponds to passing <code> -enableassertions:className</code> or <code>-ea:className
-     * </code> to the java CLI.
+     * Apply the specified transformation in class <code>className</code>.
+     *
+     * <p>If the transformation is 'enable' this corresponds to passing <code>
+     * -enableassertions:className</code> or <code>-ea:className...</code> to the java CLI.
+     *
+     * <p>If the transformation is 'disable' this corresponds to passing <code>
+     * -disableassertions:className</code> or <code>-da:className</code> to the java CLI.
      */
-    public AssertionsConfiguration.Builder enableForClass(String className) {
-      return setTransformationForClass(className, AssertionTransformation.ENABLE);
-    }
-
-    /**
-     * Disable the javac generated assertion code in class <code>className</code>. This corresponds
-     * to passing <code> -disableassertions:className</code> or <code>-da:className</code> to the
-     * java CLI.
-     */
-    public AssertionsConfiguration.Builder disableForClass(String className) {
-      return setTransformationForClass(className, AssertionTransformation.DISABLE);
-    }
-
-    public AssertionsConfiguration.Builder passthroughForClass(String className) {
-      return setTransformationForClass(className, AssertionTransformation.PASSTHROUGH);
+    public AssertionsConfiguration.Builder setScopeClass(String className) {
+      this.scope = AssertionTransformationScope.CLASS;
+      this.value = className;
+      return this;
     }
 
     /** Build and return the {@link AssertionsConfiguration}. */
     public AssertionsConfiguration build() {
-      return new AssertionsConfiguration(entries);
+      if (transformation == null) {
+        reporter.error("No transformation specified for building AccertionConfiguration");
+      }
+      if (scope == null) {
+        reporter.error("No scope specified for building AccertionConfiguration");
+      }
+      if (scope == AssertionTransformationScope.PACKAGE && value == null) {
+        reporter.error("No package name specified for building AccertionConfiguration");
+      }
+      if (scope == AssertionTransformationScope.CLASS && value == null) {
+        reporter.error("No class name specified for building AccertionConfiguration");
+      }
+      return new AssertionsConfiguration(transformation, scope, value);
+    }
+
+    /**
+     * Static helper to build an <code>AssertionConfiguration</code> which unconditionally enables
+     * javac generated assertion code in all packages and classes. To be used like this:
+     *
+     * <pre>
+     *   D8Command command = D8Command.builder()
+     *     .addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions)
+     *     ...
+     *     .build();
+     * </pre>
+     *
+     * which is a shorthand for:
+     *
+     * <pre>
+     *   D8Command command = D8Command.builder()
+     *     .addAssertionsConfiguration(builder -> builder.setEnabled().setScopeAll().build())
+     *     ...
+     *     .build();
+     * </pre>
+     */
+    public static AssertionsConfiguration enableAllAssertions(
+        AssertionsConfiguration.Builder builder) {
+      return builder.setEnable().setScopeAll().build();
+    }
+
+    /**
+     * Static helper to build an <code>AssertionConfiguration</code> which unconditionally disables
+     * javac generated assertion code in all packages and classes. To be used like this:
+     *
+     * <pre>
+     *   D8Command command = D8Command.builder()
+     *     .addAssertionsConfiguration(AssertionsConfiguration.Builder::disableAllAssertions)
+     *     ...
+     *     .build();
+     * </pre>
+     *
+     * which is a shorthand for:
+     *
+     * <pre>
+     *   D8Command command = D8Command.builder()
+     *     .addAssertionsConfiguration(builder -> builder.setDisabled().setScopeAll().build())
+     *     ...
+     *     .build();
+     * </pre>
+     */
+    public static AssertionsConfiguration disableAllAssertions(
+        AssertionsConfiguration.Builder builder) {
+      return builder.setDisable().setScopeAll().build();
+    }
+
+    /**
+     * Static helper to build an <code>AssertionConfiguration</code> which will passthrough javac
+     * generated assertion code in all packages and classes. To be used like this:
+     *
+     * <pre>
+     *   D8Command command = D8Command.builder()
+     *     .addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
+     *     ...
+     *     .build();
+     * </pre>
+     *
+     * which is a shorthand for:
+     *
+     * <pre>
+     *   D8Command command = D8Command.builder()
+     *     .addAssertionsConfiguration(builder -> builder.setPassthrough().setScopeAll().build())
+     *     ...
+     *     .build();
+     * </pre>
+     */
+    public static AssertionsConfiguration passthroughAllAssertions(
+        AssertionsConfiguration.Builder builder) {
+      return builder.setPassthrough().setScopeAll().build();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 2b94a54..fd2ecb3 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -3,7 +3,6 @@
 // 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;
@@ -40,7 +39,7 @@
   private final boolean includeClassesChecksum;
   private final boolean optimizeMultidexForLinearAlloc;
   private final BiPredicate<String, Long> dexClassChecksumFilter;
-  private final AssertionsConfiguration assertionsConfiguration;
+  private final List<AssertionsConfiguration> assertionsConfiguration;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -53,7 +52,7 @@
     includeClassesChecksum = false;
     optimizeMultidexForLinearAlloc = false;
     dexClassChecksumFilter = (name, checksum) -> true;
-    assertionsConfiguration = null;
+    assertionsConfiguration = new ArrayList<>();
   }
 
   BaseCompilerCommand(
@@ -67,7 +66,7 @@
       boolean optimizeMultidexForLinearAlloc,
       boolean includeClassesChecksum,
       BiPredicate<String, Long> dexClassChecksumFilter,
-      AssertionsConfiguration assertionsConfiguration) {
+      List<AssertionsConfiguration> assertionsConfiguration) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -135,11 +134,8 @@
     return optimizeMultidexForLinearAlloc;
   }
 
-  AssertionsConfiguration getAssertionsConfiguration(
-      AssertionTransformation defaultTransformation) {
-    return AssertionsConfiguration.builder(assertionsConfiguration)
-        .setDefault(defaultTransformation)
-        .build();
+  public List<AssertionsConfiguration> getAssertionsConfiguration() {
+    return assertionsConfiguration;
   }
 
   Reporter getReporter() {
@@ -171,7 +167,7 @@
     private boolean lookupLibraryBeforeProgram = true;
     private boolean optimizeMultidexForLinearAlloc = false;
     private BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
-    private AssertionsConfiguration assertionsConfiguration;
+    private List<AssertionsConfiguration> assertionsConfiguration = new ArrayList<>();
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -490,20 +486,19 @@
       return includeClassesChecksum;
     }
 
+    List<AssertionsConfiguration> getAssertionsConfiguration() {
+      return assertionsConfiguration;
+    }
+
     /** Configure compile time assertion enabling through a {@link AssertionsConfiguration}. */
     public B addAssertionsConfiguration(
         Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
             assertionsConfigurationGenerator) {
-      assertionsConfiguration =
-          assertionsConfigurationGenerator.apply(
-              AssertionsConfiguration.builder(assertionsConfiguration));
+      assertionsConfiguration.add(
+          assertionsConfigurationGenerator.apply(AssertionsConfiguration.builder(getReporter())));
       return self();
     }
 
-    public AssertionsConfiguration getAssertionsConfiguration() {
-      return assertionsConfiguration;
-    }
-
     @Override
     void validate() {
       Reporter reporter = getReporter();
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index d3ec378..cf5e6c8 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -159,7 +159,7 @@
 
       final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
-      if (!AssertionsRewriter.isPassthroughAll(options.assertionsConfiguration)) {
+      if (AssertionsRewriter.isEnabled(options)) {
         // 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 2401aa5..b76e6a7 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -10,11 +10,13 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.List;
 import java.util.function.BiPredicate;
 
 /**
@@ -287,7 +289,7 @@
       DesugarGraphConsumer desugarGraphConsumer,
       StringConsumer desugaredLibraryKeepRuleConsumer,
       DesugaredLibraryConfiguration libraryConfiguration,
-      AssertionsConfiguration assertionsConfiguration,
+      List<AssertionsConfiguration> assertionsConfiguration,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -361,10 +363,11 @@
     internal.desugaredLibraryConfiguration = libraryConfiguration;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
+    // Default is to remove all javac generated assertion code when generating dex.
     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);
+    internal.assertionsConfiguration =
+        new AssertionConfigurationWithDefault(
+            AssertionTransformation.DISABLE, getAssertionsConfiguration());
 
     return internal;
   }
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 792f8b9..1099cf9 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -30,7 +30,8 @@
           "--classpath",
           "--min-api",
           "--main-dex-list",
-          "--main-dex-list-output");
+          "--main-dex-list-output",
+          "--desugared-lib");
 
   private static final String APK_EXTENSION = ".apk";
   private static final String JAR_EXTENSION = ".jar";
@@ -124,6 +125,8 @@
               "                          # merging.",
               "  --file-per-class        # Produce a separate dex file per input class",
               "  --no-desugaring         # Force disable desugaring.",
+              "  --desugared-lib <file>  # Specify desugared library configuration.",
+              "                          # <file> is a desugared library configuration (json).",
               "  --main-dex-list <file>  # List of classes to place in the primary dex file.",
               "  --main-dex-list-output <file>",
               "                          # Output resulting main dex list in <file>.",
@@ -245,6 +248,8 @@
         builder.setIntermediate(true);
       } else if (arg.equals("--no-desugaring")) {
         builder.setDisableDesugaring(true);
+      } else if (arg.equals("--desugared-lib")) {
+        builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
       } else {
         if (arg.startsWith("--")) {
           builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
diff --git a/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java b/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
index d75b2b0..2210490 100644
--- a/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
+++ b/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
@@ -21,4 +21,11 @@
    * @param dependency Origin of code that is a dependency to compile {@code dependent}.
    */
   void accept(Origin dependent, Origin dependency);
+
+  /**
+   * Callback indicating no more dependency edges for the active compilation unit.
+   *
+   * <p>Note: this callback places no other guarantees on number of calls or on which threads.
+   */
+  void finished();
 }
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 50ea501..a692e1d 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.L8Command.USAGE_MESSAGE;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
 import com.android.tools.r8.dex.ApplicationReader;
@@ -15,11 +16,13 @@
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.L8TreePruner;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SelfRetraceTest;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
@@ -144,4 +147,31 @@
       }
     }
   }
+
+  private static void run(String[] args) throws CompilationFailedException {
+    L8Command command = L8Command.parse(args, CommandLineOrigin.INSTANCE).build();
+    if (command.isPrintHelp()) {
+      SelfRetraceTest.test();
+      System.out.println(USAGE_MESSAGE);
+      return;
+    }
+    if (command.isPrintVersion()) {
+      System.out.println("L8 " + Version.getVersionString());
+      return;
+    }
+    run(command);
+  }
+
+  /**
+   * Command-line entry to L8.
+   *
+   * <p>See {@link L8Command#USAGE_MESSAGE} or run {@code l8 --help} for usage information.
+   */
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      System.err.println(USAGE_MESSAGE);
+      System.exit(ExceptionUtils.STATUS_ERROR);
+    }
+    ExceptionUtils.withMainProgramHandler(() -> run(args));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 1346623..1ddd338 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -10,10 +10,12 @@
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -24,9 +26,11 @@
 @Keep
 public final class L8Command extends BaseCompilerCommand {
 
+  static final String USAGE_MESSAGE = R8CommandParser.USAGE_MESSAGE;
+
   private final D8Command d8Command;
   private final R8Command r8Command;
-  private final com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration libraryConfiguration;
+  private final DesugaredLibraryConfiguration libraryConfiguration;
   private final DexItemFactory factory;
 
   boolean isShrinking() {
@@ -41,6 +45,33 @@
     return r8Command;
   }
 
+  /**
+   * Parse the L8 command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @return L8 command builder with state set up according to parsed command line.
+   */
+  public static Builder parse(String[] args, Origin origin) {
+    return L8CommandParser.parse(args, origin);
+  }
+
+  /**
+   * Parse the L8 command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @param handler Custom defined diagnostics handler.
+   * @return L8 command builder with state set up according to parsed command line.
+   */
+  public static Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+    return L8CommandParser.parse(args, origin, handler);
+  }
+
   private L8Command(
       R8Command r8Command,
       D8Command d8Command,
@@ -63,7 +94,7 @@
         false,
         false,
         (name, checksum) -> true,
-        AssertionsConfiguration.builder(null).build());
+        ImmutableList.of());
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.libraryConfiguration = libraryConfiguration;
@@ -142,10 +173,11 @@
     // TODO(134732760): This is still work in progress.
     internal.desugaredLibraryConfiguration = libraryConfiguration;
 
+    // Default is to remove all javac generated assertion code when generating dex.
     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);
+    internal.assertionsConfiguration =
+        new AssertionConfigurationWithDefault(
+            AssertionTransformation.DISABLE, getAssertionsConfiguration());
 
     return internal;
   }
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
new file mode 100644
index 0000000..1dc83c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -0,0 +1,164 @@
+// 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;
+
+import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FlagFile;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Set;
+
+public class L8CommandParser extends BaseCompilerCommandParser {
+
+  private static final Set<String> OPTIONS_WITH_PARAMETER =
+      ImmutableSet.of("--output", "--lib", "--min-api", "--desugared-lib");
+
+  public static void main(String[] args) throws CompilationFailedException {
+    L8Command command = parse(args, Origin.root()).build();
+    if (command.isPrintHelp()) {
+      System.out.println(USAGE_MESSAGE);
+      System.exit(1);
+    }
+    L8.run(command);
+  }
+
+  static final String USAGE_MESSAGE =
+      String.join(
+          "\n",
+          Arrays.asList(
+              "Usage: l8 [options] <input-files>",
+              " where <input-files> are any combination of dex, class, zip, jar, or apk files",
+              " and options are:",
+              "  --debug                 # Compile with debugging information (default).",
+              "  --release               # Compile without debugging information.",
+              "  --output <file>         # Output result in <outfile>.",
+              "                          # <file> must be an existing directory or a zip file.",
+              "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
+              "  --min-api <number>      # Minimum Android API level compatibility, default: "
+                  + AndroidApiLevel.getDefault().getLevel()
+                  + ".",
+              "  --pg-conf <file>        # Proguard configuration <file>.",
+              "  --desugared-lib <file>  # Specify desugared library configuration.",
+              "                          # <file> is a desugared library configuration (json).",
+              "  --version               # Print the version of l8.",
+              "  --help                  # Print this message."));
+
+  /**
+   * Parse the D8 command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @return D8 command builder with state set up according to parsed command line.
+   */
+  public static L8Command.Builder parse(String[] args, Origin origin) {
+    return parse(args, origin, L8Command.builder());
+  }
+
+  /**
+   * Parse the D8 command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @param handler Custom defined diagnostics handler.
+   * @return D8 command builder with state set up according to parsed command line.
+   */
+  public static L8Command.Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+    return parse(args, origin, L8Command.builder(handler));
+  }
+
+  private static L8Command.Builder parse(String[] args, Origin origin, L8Command.Builder builder) {
+    CompilationMode compilationMode = null;
+    Path outputPath = null;
+    OutputMode outputMode = null;
+    boolean hasDefinedApiLevel = false;
+    OrderedClassFileResourceProvider.Builder classpathBuilder =
+        OrderedClassFileResourceProvider.builder();
+    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
+    for (int i = 0; i < expandedArgs.length; i++) {
+      String arg = expandedArgs[i].trim();
+      String nextArg = null;
+      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+        if (++i < expandedArgs.length) {
+          nextArg = expandedArgs[i];
+        } else {
+          builder.error(
+              new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
+          break;
+        }
+      }
+      if (arg.length() == 0) {
+        continue;
+      } else if (arg.equals("--help")) {
+        builder.setPrintHelp(true);
+      } else if (arg.equals("--version")) {
+        builder.setPrintVersion(true);
+      } else if (arg.equals("--debug")) {
+        if (compilationMode == CompilationMode.RELEASE) {
+          builder.error(
+              new StringDiagnostic("Cannot compile in both --debug and --release mode.", origin));
+          continue;
+        }
+        compilationMode = CompilationMode.DEBUG;
+      } else if (arg.equals("--release")) {
+        if (compilationMode == CompilationMode.DEBUG) {
+          builder.error(
+              new StringDiagnostic("Cannot compile in both --debug and --release mode.", origin));
+          continue;
+        }
+        compilationMode = CompilationMode.RELEASE;
+      } else if (arg.equals("--output")) {
+        if (outputPath != null) {
+          builder.error(
+              new StringDiagnostic(
+                  "Cannot output both to '" + outputPath.toString() + "' and '" + nextArg + "'",
+                  origin));
+          continue;
+        }
+        outputPath = Paths.get(nextArg);
+      } else if (arg.equals("--min-api")) {
+        if (hasDefinedApiLevel) {
+          builder.error(new StringDiagnostic("Cannot set multiple --min-api options", origin));
+        } else {
+          parseMinApi(builder, nextArg, origin);
+          hasDefinedApiLevel = true;
+        }
+      } else if (arg.equals("--lib")) {
+        addLibraryArgument(builder, origin, nextArg);
+      } else if (arg.equals("--pg-conf")) {
+        builder.addProguardConfigurationFiles(Paths.get(nextArg));
+      } else if (arg.equals("--desugared-lib")) {
+        builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
+      } else {
+        if (arg.startsWith("--")) {
+          builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
+          continue;
+        }
+        builder.addProgramFiles(Paths.get(arg));
+      }
+    }
+    if (!classpathBuilder.isEmpty()) {
+      builder.addClasspathResourceProvider(classpathBuilder.build());
+    }
+    if (compilationMode != null) {
+      builder.setMode(compilationMode);
+    }
+    if (outputMode == null) {
+      outputMode = OutputMode.DexIndexed;
+    }
+    if (outputPath == null) {
+      outputPath = Paths.get(".");
+    }
+    return builder.setOutput(outputPath, outputMode);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8c26650..dbe1572 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -807,7 +807,7 @@
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
       enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
     }
-    if (!AssertionsRewriter.isPassthroughAll(appView.options().assertionsConfiguration)) {
+    if (AssertionsRewriter.isEnabled(appView.options())) {
       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 265fa38..187e095 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -720,7 +721,7 @@
       StringConsumer desugaredLibraryKeepRuleConsumer,
       DesugaredLibraryConfiguration libraryConfiguration,
       FeatureSplitConfiguration featureSplitConfiguration,
-      AssertionsConfiguration assertionsConfiguration) {
+      List<AssertionsConfiguration> assertionsConfiguration) {
     super(
         inputApp,
         mode,
@@ -870,14 +871,14 @@
 
     internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
 
+    // Default is to remove all javac generated assertion code when generating dex.
     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()
+        new AssertionConfigurationWithDefault(
+            getProgramConsumer() instanceof ClassFileConsumer
                 ? AssertionTransformation.PASSTHROUGH
-                : AssertionTransformation.DISABLE);
+                : AssertionTransformation.DISABLE,
+            getAssertionsConfiguration());
 
     // 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/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index b0acdb6..dd9ab8e 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -25,7 +25,8 @@
           "--main-dex-list",
           "--main-dex-list-output",
           "--pg-conf",
-          "--pg-map-output");
+          "--pg-map-output",
+          "--desugared-lib");
 
   public static void main(String[] args) throws CompilationFailedException {
     R8Command command = parse(args, Origin.root()).build();
@@ -65,6 +66,8 @@
                   + ".",
               "  --pg-conf <file>         # Proguard configuration <file>.",
               "  --pg-map-output <file>   # Output the resulting name and line mapping to <file>.",
+              "  --desugared-lib <file>   # Specify desugared library configuration.",
+              "                           # <file> is a desugared library configuration (json).",
               "  --no-tree-shaking        # Force disable tree shaking of unreachable classes.",
               "  --no-minification        # Force disable minification of names.",
               "  --no-data-resources      # Ignore all data resources.",
@@ -206,6 +209,8 @@
         builder.addProguardConfigurationFiles(Paths.get(nextArg));
       } else if (arg.equals("--pg-map-output")) {
         builder.setProguardMapOutputPath(Paths.get(nextArg));
+      } else if (arg.equals("--desugared-lib")) {
+        builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
       } else if (arg.equals("--no-data-resources")) {
         state.includeDataResources = false;
       } else {
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index f54233b..d13081c 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -76,6 +76,9 @@
       case "r8":
         R8.main(shift(args));
         break;
+      case "l8":
+        L8.main(shift(args));
+        break;
       default:
         runDefault(args);
         break;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index afdc15b..4656408 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -212,7 +212,7 @@
       // default interface methods, it is expected they are targeted with invoke-direct.
       return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
     }
-    if (!encodedMethod.isVirtualMethod()) {
+    if (!encodedMethod.isNonPrivateVirtualMethod()) {
       return Type.DIRECT;
     }
     if (encodedMethod.accessFlags.isFinal()) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 7ba04b5..993363c 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -49,8 +49,8 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ObjectArrays;
-import it.unimi.dsi.fastutil.objects.Object2LongMap;
-import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2LongMap;
+import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -204,14 +204,15 @@
    */
   private void encodeChecksums(Iterable<VirtualFile> files) {
     List<DexProgramClass> classes = application.classes();
-    Object2LongMap<String> inputChecksums = new Object2LongOpenHashMap<>(classes.size());
+    Reference2LongMap<DexString> inputChecksums = new Reference2LongOpenHashMap<>(classes.size());
     for (DexProgramClass clazz : classes) {
-      inputChecksums.put(clazz.getType().descriptor.toASCIIString(), clazz.getChecksum());
+      inputChecksums.put(clazz.getType().descriptor, clazz.getChecksum());
     }
     for (VirtualFile file : files) {
       ClassesChecksum toWrite = new ClassesChecksum();
-      for (String desc : file.getClassDescriptors()) {
-        toWrite.addChecksum(desc, inputChecksums.getLong(desc));
+      for (DexProgramClass clazz : file.classes()) {
+        DexString desc = clazz.type.descriptor;
+        toWrite.addChecksum(desc.toString(), inputChecksums.getLong(desc));
       }
       file.injectString(application.dexItemFactory.createString(toWrite.toJsonString()));
     }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index d5e338e..c69d8e0 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -716,7 +716,7 @@
 
       Long checksum = null;
       if (checksums != null && !checksums.isEmpty()) {
-        String desc = type.descriptor.toASCIIString();
+        String desc = type.toDescriptorString();
         checksum = checksums.getOrDefault(desc, null);
         if (!options.dexClassChecksumFilter.test(desc, checksum)) {
           continue;
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 86b2cb8..ad19ed4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -256,16 +256,17 @@
 
   /**
    * Lookup virtual method starting in type and following the super chain.
-   * <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 a non-static, non-private 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 a virtual target.
+   *
+   * <p>TODO(b/140204899): Delete this method as it does resolution and not a "lookup of targets".
    */
   public DexEncodedMethod lookupVirtualTarget(DexType type, DexMethod method) {
     assert checkIfObsolete();
     assert type.isClassType() || type.isArrayType();
     ResolutionResult resolutionResult = resolveMethod(type, method);
-    DexEncodedMethod target = resolutionResult.getSingleTarget();
-    return target == null || target.isVirtualMethod() ? target : null;
+    return resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
   }
 
   /**
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 dbd455b..a928529 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -221,11 +221,11 @@
   }
 
   public OptionalBool isLibraryMethodOverride() {
-    return isVirtualMethod() ? isLibraryMethodOverride : OptionalBool.FALSE;
+    return isNonPrivateVirtualMethod() ? isLibraryMethodOverride : OptionalBool.FALSE;
   }
 
   public void setLibraryMethodOverride(OptionalBool isLibraryMethodOverride) {
-    assert isVirtualMethod();
+    assert isNonPrivateVirtualMethod();
     assert !isLibraryMethodOverride.isUnknown();
     assert isLibraryMethodOverride.isPossiblyFalse()
             || this.isLibraryMethodOverride.isPossiblyTrue()
@@ -287,12 +287,19 @@
   }
 
   /**
-   * Returns true if this method can be invoked via invoke-virtual, invoke-super or
-   * invoke-interface.
+   * Returns true if this method can be invoked via invoke-virtual/interface.
+   *
+   * <p>Note that also private methods can be the target of a virtual invoke. In such cases, the
+   * validity of the invoke depends on the access granted to the call site.
    */
   public boolean isVirtualMethod() {
     checkIfObsolete();
-    return !accessFlags.isStatic() && !accessFlags.isPrivate() && !accessFlags.isConstructor();
+    return !accessFlags.isStatic() && !accessFlags.isConstructor();
+  }
+
+  public boolean isNonPrivateVirtualMethod() {
+    checkIfObsolete();
+    return !isPrivateMethod() && isVirtualMethod();
   }
 
   /**
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 bbed9bc..9a8a73c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -82,7 +82,14 @@
   public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
     if (isClassType()) {
       DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(this));
-      return clazz != null && !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
+      if (clazz == null) {
+        return false;
+      }
+      if (appView.options().enableUninstantiatedTypeOptimizationForInterfaces) {
+        return !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
+      } else {
+        return !clazz.isInterface() && !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
+      }
     }
     return false;
   }
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 3080960..4854741 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -3,10 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
 import java.util.Collection;
@@ -57,9 +54,9 @@
   public abstract boolean isAccessibleForVirtualDispatchFrom(
       DexProgramClass context, AppInfoWithSubtyping appInfo);
 
-  public abstract boolean isValidVirtualTarget(InternalOptions options);
-
-  public abstract boolean isValidVirtualTargetForDynamicDispatch();
+  // TODO(b/145187573): Remove this and use proper access checks.
+  @Deprecated
+  public abstract boolean isVirtualTarget();
 
   /** Lookup the single target of an invoke-special on this resolution result if possible. */
   public abstract DexEncodedMethod lookupInvokeSpecialTarget(
@@ -87,12 +84,6 @@
     private final DexClass resolvedHolder;
     private final DexEncodedMethod resolvedMethod;
 
-    public static boolean isValidVirtualTarget(InternalOptions options, DexEncodedMethod target) {
-      return options.canUseNestBasedAccess()
-          ? (!target.accessFlags.isStatic() && !target.accessFlags.isConstructor())
-          : target.isVirtualMethod();
-    }
-
     public SingleResolutionResult(
         DexClass initialResolutionHolder,
         DexClass resolvedHolder,
@@ -133,20 +124,11 @@
     @Override
     public boolean isAccessibleForVirtualDispatchFrom(
         DexProgramClass context, AppInfoWithSubtyping appInfo) {
-      // If a private method is accessible (which implies it is via its nest), then it is a valid
-      // virtual dispatch target if non-static.
-      return isAccessibleFrom(context, appInfo)
-          && (resolvedMethod.isVirtualMethod()
-              || (resolvedMethod.isPrivateMethod() && !resolvedMethod.isStatic()));
+      return resolvedMethod.isVirtualMethod() && isAccessibleFrom(context, appInfo);
     }
 
     @Override
-    public boolean isValidVirtualTarget(InternalOptions options) {
-      return isValidVirtualTarget(options, resolvedMethod);
-    }
-
-    @Override
-    public boolean isValidVirtualTargetForDynamicDispatch() {
+    public boolean isVirtualTarget() {
       return resolvedMethod.isVirtualMethod();
     }
 
@@ -301,12 +283,16 @@
     @Override
     // TODO(b/140204899): Leverage refined receiver type if available.
     public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
-      assert isValidVirtualTarget(appInfo.app().options);
+      if (resolvedMethod.isPrivateMethod()) {
+        // If the resolved reference is private there is no dispatch.
+        // This is assuming that the method is accessible, which implies self/nest access.
+        return Collections.singleton(resolvedMethod);
+      }
+      assert resolvedMethod.isNonPrivateVirtualMethod();
       // First add the target for receiver type method.type.
-      DexEncodedMethod encodedMethod = getSingleTarget();
-      Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
+      Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(resolvedMethod);
       // Add all matching targets from the subclass hierarchy.
-      DexMethod method = encodedMethod.method;
+      DexMethod method = resolvedMethod.method;
       // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
       //   receiver type if available.
       for (DexType type : appInfo.subtypes(method.holder)) {
@@ -314,7 +300,7 @@
         if (!clazz.isInterface()) {
           ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method);
           DexEncodedMethod target = methods.getSingleTarget();
-          if (target != null && target.isVirtualMethod()) {
+          if (target != null && target.isNonPrivateVirtualMethod()) {
             result.add(target);
           }
         }
@@ -325,43 +311,44 @@
     @Override
     // TODO(b/140204899): Leverage refined receiver type if available.
     public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
-      assert isValidVirtualTarget(appInfo.app().options);
+      if (resolvedMethod.isPrivateMethod()) {
+        // If the resolved reference is private there is no dispatch.
+        // This is assuming that the method is accessible, which implies self/nest access.
+        assert resolvedMethod.hasCode();
+        return Collections.singleton(resolvedMethod);
+      }
+      assert resolvedMethod.isNonPrivateVirtualMethod();
       Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
-      if (isSingleResolution()) {
-        // Add default interface methods to the list of targets.
-        //
-        // This helps to make sure we take into account synthesized lambda classes
-        // that we are not aware of. Like in the following example, we know that all
-        // classes, XX in this case, override B::bar(), but there are also synthesized
-        // classes for lambda which don't, so we still need default method to be live.
-        //
-        //   public static void main(String[] args) {
-        //     X x = () -> {};
-        //     x.bar();
-        //   }
-        //
-        //   interface X {
-        //     void foo();
-        //     default void bar() { }
-        //   }
-        //
-        //   class XX implements X {
-        //     public void foo() { }
-        //     public void bar() { }
-        //   }
-        //
-        DexEncodedMethod singleTarget = getSingleTarget();
-        if (singleTarget.hasCode()) {
-          DexProgramClass holder =
-              asProgramClassOrNull(appInfo.definitionFor(singleTarget.method.holder));
-          if (appInfo.hasAnyInstantiatedLambdas(holder)) {
-            result.add(singleTarget);
-          }
+      // Add default interface methods to the list of targets.
+      //
+      // This helps to make sure we take into account synthesized lambda classes
+      // that we are not aware of. Like in the following example, we know that all
+      // classes, XX in this case, override B::bar(), but there are also synthesized
+      // classes for lambda which don't, so we still need default method to be live.
+      //
+      //   public static void main(String[] args) {
+      //     X x = () -> {};
+      //     x.bar();
+      //   }
+      //
+      //   interface X {
+      //     void foo();
+      //     default void bar() { }
+      //   }
+      //
+      //   class XX implements X {
+      //     public void foo() { }
+      //     public void bar() { }
+      //   }
+      //
+      if (resolvedMethod.hasCode()) {
+        DexProgramClass holder = resolvedHolder.asProgramClass();
+        if (appInfo.hasAnyInstantiatedLambdas(holder)) {
+          result.add(resolvedMethod);
         }
       }
 
-      DexEncodedMethod encodedMethod = getSingleTarget();
-      DexMethod method = encodedMethod.method;
+      DexMethod method = resolvedMethod.method;
       Consumer<DexEncodedMethod> addIfNotAbstract =
           m -> {
             if (!m.accessFlags.isAbstract()) {
@@ -451,12 +438,7 @@
     }
 
     @Override
-    public boolean isValidVirtualTarget(InternalOptions options) {
-      return true;
-    }
-
-    @Override
-    public boolean isValidVirtualTargetForDynamicDispatch() {
+    public boolean isVirtualTarget() {
       return true;
     }
   }
@@ -490,12 +472,7 @@
     }
 
     @Override
-    public boolean isValidVirtualTarget(InternalOptions options) {
-      return false;
-    }
-
-    @Override
-    public boolean isValidVirtualTargetForDynamicDispatch() {
+    public boolean isVirtualTarget() {
       return false;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index d1c82d4..71750f1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -42,7 +42,7 @@
    */
   @Override
   public boolean registerConstClass(DexType type) {
-    if (references.isDynamicMethod(currentMethod)) {
+    if (references.isDynamicMethod(getContextMethod())) {
       return false;
     }
     return super.registerConstClass(type);
@@ -57,7 +57,7 @@
    */
   @Override
   public boolean registerStaticFieldRead(DexField field) {
-    if (references.isDynamicMethod(currentMethod)) {
+    if (references.isDynamicMethod(getContextMethod())) {
       return false;
     }
     return super.registerStaticFieldRead(field);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index c7939b5..ade31ca 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -180,7 +180,7 @@
               method -> {
                 ResolutionResult resolution =
                     appView.appInfo().resolveMethod(method.holder, method, isInterface);
-                if (resolution.isValidVirtualTarget(appView.options())) {
+                if (resolution.isVirtualTarget()) {
                   return resolution.lookupVirtualDispatchTargets(isInterface, appView.appInfo());
                 }
                 return null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
index 5d73cd5..8a40db4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
@@ -6,9 +6,14 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.function.Consumer;
 
 /**
- * An abstraction of {@link IRCode}-level optimization.
+ * An abstraction of {@link IRCode}-level optimization, which may retrieve info from
+ * {@link AppView}; update {@link OptimizationFeedback}; or utilize {@link MethodProcessor}.
  */
 public interface CodeOptimization {
 
@@ -17,9 +22,61 @@
   //  rewriting every affected optimization.
   // Note that a code optimization can be a collection of other code optimizations.
   // In that way, IRConverter will serve as the default full processing of all optimizations.
-  void optimize(
-      AppView<?> appView,
-      IRCode code,
-      OptimizationFeedback feedback,
-      MethodProcessor methodProcessor);
+  void optimize(IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor);
+
+  static CodeOptimization from(Consumer<IRCode> consumer) {
+    return (code, feedback, methodProcessor) -> {
+      consumer.accept(code);
+    };
+  }
+
+  static CodeOptimization sequence(CodeOptimization... codeOptimizations) {
+    return sequence(Arrays.asList(codeOptimizations));
+  }
+
+  static CodeOptimization sequence(Collection<CodeOptimization> codeOptimizations) {
+    return (code, feedback, methodProcessor) -> {
+      for (CodeOptimization codeOptimization : codeOptimizations) {
+        codeOptimization.optimize(code, feedback, methodProcessor);
+      }
+    };
+  }
+
+  /**
+   * Builder for {@link CodeOptimization}.
+   *
+   * Users can append either {@link CodeOptimization}, or simply {@link IRCode} consumer.
+   *
+   * Note that the order of everything that is appended through the builder matters.
+   */
+  public static class Builder {
+    private ImmutableList.Builder<CodeOptimization> processingQueue;
+
+    private Builder() {
+      processingQueue = ImmutableList.builder();
+    }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+
+    public Builder addIRCodeConsumer(Consumer<IRCode> consumer) {
+      processingQueue.add(from(consumer));
+      return this;
+    }
+
+    public Builder addCodeOptimization(CodeOptimization optimization) {
+      processingQueue.add(optimization);
+      return this;
+    }
+
+    public CodeOptimization build() {
+      return (code, feedback, methodProcessor) -> {
+        processingQueue
+            .build()
+            .forEach(codeOptimization ->
+                codeOptimization.optimize(code, feedback, methodProcessor));
+      };
+    }
+  }
 }
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 00d2f79..b3a6d66 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
@@ -1092,12 +1092,11 @@
       feedback.markProcessed(method, ConstraintWithTarget.NEVER);
       return;
     }
-    optimize(appView, code, feedback, methodProcessor);
+    optimize(code, feedback, methodProcessor);
   }
 
   // TODO(b/140766440): Convert all sub steps an implementer of CodeOptimization
   private void optimize(
-      AppView<?> appView,
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor) {
@@ -1218,7 +1217,7 @@
       stringOptimizer.computeTrivialOperationsOnConstString(code);
       stringOptimizer.removeTrivialConversions(code);
       if (libraryMethodOptimizer != null) {
-        libraryMethodOptimizer.optimize(appView, code, feedback, methodProcessor);
+        libraryMethodOptimizer.optimize(code, feedback, methodProcessor);
       }
       assert code.isConsistentSSA();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index bf556c8..dc425e4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -643,7 +643,8 @@
   }
 
   private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
-    return target.isVirtualMethod() && appView.isInterface(target.method.holder).isFalse();
+    return target.isNonPrivateVirtualMethod()
+        && appView.isInterface(target.method.holder).isFalse();
   }
 
   private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 9ecc62d..7e0c9d5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -153,7 +153,7 @@
     }
     // TODO(b/140768815): Reprocessing may trigger more methods to revisit. Update waves on-the-fly.
     for (CodeOptimization codeOptimization : codeOptimizations) {
-      codeOptimization.optimize(appView, code, feedback, this);
+      codeOptimization.optimize(code, feedback, this);
     }
   }
 }
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 a4fc2e9..565af2f 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
@@ -333,7 +333,7 @@
   private boolean isRetargetMethod(DexLibraryClass holder, DexEncodedMethod method) {
     assert needsLibraryInfo();
     assert holder.type == method.method.holder;
-    assert method.isVirtualMethod();
+    assert method.isNonPrivateVirtualMethod();
     if (method.isFinal()) {
       return false;
     }
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 7f4c043..328d8e2 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
@@ -26,7 +26,6 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -397,15 +396,17 @@
     if (appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
       return null;
     }
-    ResolutionResult resolutionResult =
-        appView.appInfo().resolveMaximallySpecificMethods(dexClass, invokedMethod);
-    if (!resolutionResult.isSingleResolution()) {
+    DexEncodedMethod singleTarget =
+        appView
+            .appInfo()
+            .resolveMaximallySpecificMethods(dexClass, invokedMethod)
+            .getSingleTarget();
+    if (singleTarget == null) {
       // At this point we are in a library class. Failures can happen with NoSuchMethod if a
       // library class implement a method with same signature but not related to emulated
       // interfaces.
       return null;
     }
-    DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
     if (!singleTarget.isAbstract() && isEmulatedInterface(singleTarget.method.holder)) {
       return singleTarget.method.holder;
     }
@@ -1143,10 +1144,16 @@
     assert !dependency.isLibraryClass();
     DesugarGraphConsumer consumer = options.desugarGraphConsumer;
     if (consumer != null) {
-      Origin dependentOrigin = dependent.getOrigin();
       Origin dependencyOrigin = dependency.getOrigin();
-      if (dependentOrigin != dependencyOrigin) {
-        consumer.accept(dependentOrigin, dependencyOrigin);
+      java.util.Collection<DexProgramClass> dependents = dependent.getSynthesizedFrom();
+      if (dependents == null || dependents.isEmpty()) {
+        dependents = Collections.singletonList(dependent);
+      }
+      for (DexProgramClass clazz : dependents) {
+        Origin dependentOrigin = clazz.getOrigin();
+        if (dependentOrigin != dependencyOrigin) {
+          consumer.accept(dependentOrigin, dependencyOrigin);
+        }
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 30edf6a..cf66050 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.Pair;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -90,7 +91,7 @@
   }
 
   // Extract the list of types in the programClass' nest, of host hostClass
-  private List<DexType> extractNest(DexClass clazz) {
+  private Pair<DexClass, List<DexType>> extractNest(DexClass clazz) {
     assert clazz != null;
     DexClass hostClass = clazz.isNestHost() ? clazz : definitionFor(clazz.getNestHost());
     if (hostClass == null) {
@@ -105,22 +106,22 @@
       classesInNest.add(nestmate.getNestMember());
     }
     classesInNest.add(hostClass.type);
-    return classesInNest;
+    return new Pair<>(hostClass, classesInNest);
   }
 
   Future<?> asyncProcessNest(DexClass clazz, ExecutorService executorService) {
     return executorService.submit(
         () -> {
-          List<DexType> nest = extractNest(clazz);
+          Pair<DexClass, List<DexType>> nest = extractNest(clazz);
           // Nest is null when nest host is missing, we do nothing in this case.
           if (nest != null) {
-            processNest(nest);
+            processNest(nest.getFirst(), nest.getSecond());
           }
           return null; // we want a Callable not a Runnable to be able to throw
         });
   }
 
-  private void processNest(List<DexType> nest) {
+  private void processNest(DexClass host, List<DexType> nest) {
     boolean reported = false;
     for (DexType type : nest) {
       DexClass clazz = definitionFor(type);
@@ -130,6 +131,7 @@
           reported = true;
         }
       } else {
+        reportDesugarDependencies(host, clazz);
         if (shouldProcessClassInNest(clazz, nest)) {
           NestBasedAccessDesugaringUseRegistry registry =
               new NestBasedAccessDesugaringUseRegistry(clazz);
@@ -142,6 +144,18 @@
     }
   }
 
+  private void reportDesugarDependencies(DexClass host, DexClass clazz) {
+    if (host == clazz) {
+      return;
+    }
+    if (host.isProgramClass()) {
+      InterfaceMethodRewriter.reportDependencyEdge(host.asProgramClass(), clazz, appView.options());
+    }
+    if (clazz.isProgramClass()) {
+      InterfaceMethodRewriter.reportDependencyEdge(clazz.asProgramClass(), host, appView.options());
+    }
+  }
+
   protected abstract boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest);
 
   private DexProgramClass createNestAccessConstructor() {
@@ -272,9 +286,9 @@
       return true;
     }
     assert holder.isLibraryClass();
-    List<DexType> nest = extractNest(holder);
+    Pair<DexClass, List<DexType>> nest = extractNest(holder);
     assert nest != null : "Should be a compilation error if missing nest host on library class.";
-    reportIncompleteNest(nest);
+    reportIncompleteNest(nest.getSecond());
     throw new Unreachable(
         "Incomplete nest due to missing library class should raise a compilation error.");
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
index babdbae..9fca058 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
@@ -41,9 +41,9 @@
     assert methodMap instanceof IdentityHashMap;
     assert getFieldMap instanceof IdentityHashMap;
     assert putFieldMap instanceof IdentityHashMap;
+    this.nestConstructorType = nestConstructorType;
     this.getFieldMap = getFieldMap;
     this.putFieldMap = putFieldMap;
-    this.nestConstructorType = nestConstructorType;
   }
 
   private DexMethod lookupFieldForMethod(
@@ -111,11 +111,8 @@
   @Override
   public GraphLenseLookupResult lookupMethod(
       DexMethod method, DexMethod context, Invoke.Type type) {
-    DexMethod previousContext =
-        originalMethodSignatures != null
-            ? originalMethodSignatures.getOrDefault(context, context)
-            : context;
-    GraphLenseLookupResult previous = previousLense.lookupMethod(method, previousContext, type);
+    assert originalMethodSignatures == null;
+    GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
     DexMethod bridge = methodMap.get(previous.getMethod());
     if (bridge == null) {
       return previous;
@@ -130,4 +127,31 @@
     return new GraphLenseLookupResult(bridge, Invoke.Type.STATIC);
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder extends NestedGraphLense.Builder {
+
+    private Map<DexField, DexMethod> getFieldMap = new IdentityHashMap<>();
+    private Map<DexField, DexMethod> putFieldMap = new IdentityHashMap<>();
+
+    public void mapGetField(DexField from, DexMethod to) {
+      getFieldMap.put(from, to);
+    }
+
+    public void mapPutField(DexField from, DexMethod to) {
+      putFieldMap.put(from, to);
+    }
+
+    public GraphLense build(AppView<?> appView, DexType nestConstructorType) {
+      assert typeMap.isEmpty();
+      assert fieldMap.isEmpty();
+      if (getFieldMap.isEmpty() && methodMap.isEmpty() && putFieldMap.isEmpty()) {
+        return appView.graphLense();
+      }
+      return new NestedPrivateMethodLense(
+          appView, nestConstructorType, methodMap, getFieldMap, putFieldMap, appView.graphLense());
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index a4642cd..1a6c365 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 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.DexType;
@@ -16,13 +15,13 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.function.BiConsumer;
 
 // Summary:
 // - Computes all the live nests reachable from Program Classes (Sequential), each time a
@@ -31,10 +30,6 @@
 // for the lens (Sequential)
 public class R8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
 
-  private final Map<DexMethod, DexMethod> lensBridges = new IdentityHashMap<>();
-  private final Map<DexField, DexMethod> lensGetFieldBridges = new IdentityHashMap<>();
-  private final Map<DexField, DexMethod> lensPutFieldBridges = new IdentityHashMap<>();
-
   public R8NestBasedAccessDesugaring(AppView<?> appView) {
     super(appView);
   }
@@ -44,43 +39,27 @@
     assert !appView.options().canUseNestBasedAccess()
         || appView.options().testing.enableForceNestBasedAccessDesugaringForTest;
     computeAndProcessNestsConcurrently(executorService);
-    addDeferredBridgesAndMapMethods();
+    NestedPrivateMethodLense.Builder lensBuilder = NestedPrivateMethodLense.builder();
+    addDeferredBridgesAndMapMethods(lensBuilder);
     clearNestAttributes();
-    if (nothingToMap()) {
-      return appView.graphLense();
-    }
     synthesizeNestConstructor(appBuilder);
-    return new NestedPrivateMethodLense(
-        appView,
-        getNestConstructorType(),
-        lensBridges,
-        lensGetFieldBridges,
-        lensPutFieldBridges,
-        appView.graphLense());
+    return lensBuilder.build(appView, getNestConstructorType());
   }
 
-  private boolean nothingToMap() {
-    return lensBridges.isEmpty() && lensGetFieldBridges.isEmpty() && lensPutFieldBridges.isEmpty();
-  }
-
-  private void addDeferredBridgesAndMapMethods() {
+  private void addDeferredBridgesAndMapMethods(NestedPrivateMethodLense.Builder lensBuilder) {
     // Here we add the bridges and we fill the lens map.
-    // The lens map are different than the original map since
-    // they refer DexMethod and not DexEncodedMethod (so they can be long lived without issues),
-    // and since they do not require synchronization (they are only read in the lens).
-    // We cannot easily do this concurrently since methods are added to classes.
-    addDeferredBridgesAndMapMethods(bridges, lensBridges);
-    addDeferredBridgesAndMapMethods(getFieldBridges, lensGetFieldBridges);
-    addDeferredBridgesAndMapMethods(putFieldBridges, lensPutFieldBridges);
+    addDeferredBridgesAndMapMethods(bridges, lensBuilder::map);
+    addDeferredBridgesAndMapMethods(getFieldBridges, lensBuilder::mapGetField);
+    addDeferredBridgesAndMapMethods(putFieldBridges, lensBuilder::mapPutField);
   }
 
   private <E> void addDeferredBridgesAndMapMethods(
-      Map<E, DexEncodedMethod> bridges, Map<E, DexMethod> map) {
+      Map<E, DexEncodedMethod> bridges, BiConsumer<E, DexMethod> lensInserter) {
     for (Map.Entry<E, DexEncodedMethod> entry : bridges.entrySet()) {
       DexClass holder = definitionFor(entry.getValue().method.holder);
       assert holder != null && holder.isProgramClass();
       holder.asProgramClass().addMethod(entry.getValue());
-      map.put(entry.getKey(), entry.getValue().method);
+      lensInserter.accept(entry.getKey(), entry.getValue().method);
     }
     bridges.clear();
   }
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
index 2292b0a..a344095 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -6,9 +6,6 @@
 
 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;
@@ -21,7 +18,9 @@
 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.AssertionConfigurationWithDefault;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingCharIterator;
 import java.io.UTFDataFormatException;
 import java.util.List;
@@ -31,21 +30,21 @@
 
   private static class ConfigurationEntryWithDexString {
 
-    private ConfigurationEntry entry;
+    private AssertionsConfiguration entry;
     private final DexString value;
 
     private ConfigurationEntryWithDexString(
-        ConfigurationEntry entry, DexItemFactory dexItemFactory) {
-      this.entry = entry;
-      switch (entry.getType()) {
+        AssertionsConfiguration configuration, DexItemFactory dexItemFactory) {
+      this.entry = configuration;
+      switch (configuration.getScope()) {
         case PACKAGE:
-          if (entry.getValue().length() == 0) {
+          if (configuration.getValue().length() == 0) {
             value = dexItemFactory.createString("");
           } else {
             value =
                 dexItemFactory.createString(
                     "L"
-                        + entry
+                        + configuration
                             .getValue()
                             .replace(
                                 DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
@@ -57,7 +56,7 @@
           value =
               dexItemFactory.createString(
                   "L"
-                      + entry
+                      + configuration
                           .getValue()
                           .replace(
                               DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
@@ -75,39 +74,38 @@
 
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
+  private final AssertionTransformation defaultTransformation;
   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);
+    this.enabled = isEnabled(appView.options());
+    if (!enabled) {
+      defaultTransformation = null;
+      configuration = null;
+      return;
     }
+    // Convert the assertion transformation to the representation used for this rewriter.
+    this.defaultTransformation = appView.options().assertionsConfiguration.defautlTransformation;
+    this.configuration =
+        appView.options().assertionsConfiguration.assertionsConfigurations.stream()
+            .map(entry -> new ConfigurationEntryWithDexString(entry, appView.dexItemFactory()))
+            .collect(Collectors.toList());
   }
 
-  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;
+  // Static method used by other analyses to see if additional analysis is required to support
+  // this rewriting.
+  public static boolean isEnabled(InternalOptions options) {
+    AssertionConfigurationWithDefault configuration = options.assertionsConfiguration;
+    return configuration != null && !configuration.isPassthroughAll();
   }
 
   private AssertionTransformation getTransformationForMethod(DexEncodedMethod method) {
-    AssertionTransformation transformation = null;
+    AssertionTransformation transformation = defaultTransformation;
     for (ConfigurationEntryWithDexString entry : configuration) {
-      switch (entry.entry.getType()) {
+      switch (entry.entry.getScope()) {
         case ALL:
           transformation = entry.entry.getTransformation();
           break;
@@ -132,7 +130,7 @@
           throw new Unreachable();
       }
     }
-    assert transformation != null; // Default transformation are always added.
+    assert transformation != null;
     return transformation;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index e83a17a..00c797d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -88,7 +88,7 @@
           ResolutionResult resolutionResult =
               appView.appInfo().resolveMethod(invokedMethod.holder, invokedMethod);
           // For virtual and interface calls, proceed on valid results only (since it's enforced).
-          if (!resolutionResult.isValidVirtualTarget(appView.options())) {
+          if (!resolutionResult.isVirtualTarget()) {
             continue;
           }
           // If the resolution ended up with a single target, check if it is a library override.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 4977d4c..b95f69e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -352,7 +352,7 @@
     // resolution result.
     ResolutionResult resolutionResult =
         appView.appInfo().resolveMethod(method.holder, method, isInterface);
-    if (!resolutionResult.isValidVirtualTarget(appView.options())) {
+    if (!resolutionResult.isVirtualTarget()) {
       return ConstraintWithTarget.NEVER;
     }
 
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 194b84a..aec5219 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
@@ -291,7 +291,7 @@
     if (method.getCode() == null || !method.getCode().isCfCode()) {
       return null;
     }
-    if (method.isVirtualMethod()) {
+    if (method.isNonPrivateVirtualMethod()) {
       // Abort if the method overrides another method, or if the method is overridden. In both cases
       // an unused argument cannot be removed unless it is unused in all of the related methods in
       // the hierarchy.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index c522ff3..d4d43f4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -859,13 +859,14 @@
 
     // We should not inline a method if the invocation has type interface or virtual and the
     // signature of the invocation resolves to a private or static method.
+    // TODO(b/147212189): Why not inline private methods? If access is permitted it is valid.
     ResolutionResult resolutionResult = appView.appInfo().resolveMethod(callee.holder, callee);
     if (resolutionResult.isSingleResolution()
-        && !resolutionResult.getSingleTarget().isVirtualMethod()) {
+        && !resolutionResult.getSingleTarget().isNonPrivateVirtualMethod()) {
       return null;
     }
 
-    if (!singleTarget.isVirtualMethod()) {
+    if (!singleTarget.isNonPrivateVirtualMethod()) {
       return null;
     }
     if (method == singleTarget) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index a11f309..8fee6c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -135,7 +135,7 @@
         return;
       }
 
-      assert method.isVirtualMethod();
+      assert method.isNonPrivateVirtualMethod();
       assert context == null;
 
       Map<InvokeVirtual, InliningInfo> invokesToInline = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
index 0a92679..9c5a8d1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -24,10 +24,13 @@
 
 public class LibraryMethodOptimizer implements CodeOptimization {
 
+  private final AppView<?> appView;
+
   private final Map<DexType, LibraryMethodModelCollection> libraryMethodModelCollections =
       new IdentityHashMap<>();
 
   public LibraryMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+    this.appView = appView;
     register(new BooleanMethodOptimizer(appView));
   }
 
@@ -39,7 +42,6 @@
 
   @Override
   public void optimize(
-      AppView<?> appView,
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 79d9721..057875d 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.ExceptionUtils.STATUS_ERROR;
 
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -35,7 +36,7 @@
 
   public static final String USAGE_MESSAGE =
       StringUtils.lines(
-          "Usage: retrace <proguard-map> <stacktrace-file>",
+          "Usage: retrace <proguard-map> <stacktrace-file> [--regex <regexp>, --verbose, --info]",
           "  where <proguard-map> is an r8 generated mapping file.");
 
   private static Builder parseArguments(String[] args, DiagnosticsHandler diagnosticsHandler) {
@@ -48,16 +49,19 @@
       if (help != null) {
         return null;
       }
+      Boolean info = OptionsParsing.tryParseBoolean(context, "--info");
+      if (info != null) {
+        // This is already set in the diagnostics handler.
+        continue;
+      }
       Boolean verbose = OptionsParsing.tryParseBoolean(context, "--verbose");
       if (verbose != null) {
-        // TODO(b/132850880): Enable support for verbose.
-        diagnosticsHandler.error(new StringDiagnostic("Currently no support for --verbose"));
+        builder.setVerbose(true);
         continue;
       }
       String regex = OptionsParsing.tryParseSingle(context, "--regex", "r");
       if (regex != null && !regex.isEmpty()) {
-        // TODO(b/132850880): Enable support for regex.
-        diagnosticsHandler.error(new StringDiagnostic("Currently no support for --regex"));
+        builder.setRegularExpression(regex);
         continue;
       }
       if (!hasSetProguardMap) {
@@ -108,7 +112,7 @@
   }
 
   /**
-   * The main entry point for running the retrace.
+   * The main entry point for running retrace.
    *
    * @param command The command that describes the desired behavior of this retrace invocation.
    */
@@ -128,7 +132,8 @@
                 .retrace();
       } else {
         result =
-            new RetraceStackTrace(retraceBase, command.stackTrace, command.diagnosticsHandler)
+            new RetraceStackTrace(
+                    retraceBase, command.stackTrace, command.diagnosticsHandler, command.isVerbose)
                 .retrace();
       }
       command.retracedStackTraceConsumer.accept(result.getNodes());
@@ -139,9 +144,11 @@
     }
   }
 
-  static void run(String[] args) {
-    DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
-    Builder builder = parseArguments(args, diagnosticsHandler);
+  public static void run(String[] args) {
+    RetraceDiagnosticsHandler retraceDiagnosticsHandler =
+        new RetraceDiagnosticsHandler(
+            new DiagnosticsHandler() {}, Arrays.asList(args).contains("--info"));
+    Builder builder = parseArguments(args, retraceDiagnosticsHandler);
     if (builder == null) {
       // --help was an argument to list
       assert Arrays.asList(args).contains("--help");
@@ -152,6 +159,7 @@
         retraced -> System.out.print(StringUtils.lines(retraced)));
     run(builder.build());
   }
+
   /**
    * The main entry point for running a legacy compatible retrace from the command line.
    *
@@ -189,4 +197,33 @@
       System.exit(STATUS_ERROR);
     }
   }
+
+  private static class RetraceDiagnosticsHandler implements DiagnosticsHandler {
+
+    private final DiagnosticsHandler diagnosticsHandler;
+    private final boolean printInfo;
+
+    public RetraceDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler, boolean printInfo) {
+      this.diagnosticsHandler = diagnosticsHandler;
+      this.printInfo = printInfo;
+      assert diagnosticsHandler != null;
+    }
+
+    @Override
+    public void error(Diagnostic error) {
+      diagnosticsHandler.error(error);
+    }
+
+    @Override
+    public void warning(Diagnostic warning) {
+      diagnosticsHandler.warning(warning);
+    }
+
+    @Override
+    public void info(Diagnostic info) {
+      if (printInfo) {
+        diagnosticsHandler.info(info);
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index b2692a2..93addc4 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.StringDiagnostic;
 import java.io.IOException;
 import java.util.List;
 import java.util.function.Consumer;
@@ -56,7 +57,7 @@
   public static class Builder {
 
     private boolean isVerbose;
-    private DiagnosticsHandler diagnosticsHandler;
+    private final DiagnosticsHandler diagnosticsHandler;
     private ProguardMapProducer proguardMapProducer;
     private String regularExpression;
     private List<String> stackTrace;
@@ -67,8 +68,8 @@
     }
 
     /** Set if the produced stack trace should have additional information. */
-    public Builder isVerbose() {
-      this.isVerbose = true;
+    public Builder setVerbose(boolean verbose) {
+      this.isVerbose = verbose;
       return this;
     }
 
@@ -127,6 +128,11 @@
       if (this.retracedStackTraceConsumer == null) {
         throw new RuntimeException("RetracedStackConsumer not specified");
       }
+      if (isVerbose && regularExpression != null) {
+        this.diagnosticsHandler.warning(
+            new StringDiagnostic(
+                "Retrace does not support verbose output when a regular expression is specified"));
+      }
       return new RetraceCommand(
           isVerbose,
           regularExpression,
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 3a564d8..3b68d3d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.retrace.RetraceUtils.methodDescriptionFromMethodReference;
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.DiagnosticsHandler;
@@ -11,7 +12,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.google.common.base.Strings;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
@@ -42,17 +43,21 @@
       if (lines.get(0).asAtLine().isAmbiguous) {
         lines.sort(new AtStackTraceLineComparator());
       }
-      String previousClazz = "";
+      boolean shouldPrintOr = false;
       for (StackTraceLine line : lines) {
         assert line.isAtLine();
         AtLine atLine = line.asAtLine();
-        if (atLine.isAmbiguous) {
+        if (atLine.isAmbiguous && shouldPrintOr) {
+          String atLineString = atLine.toString();
+          int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(atLineString);
           strings.add(
-              atLine.toString(previousClazz.isEmpty() ? atLine.at : "<OR> " + atLine.at, ""));
+              atLineString.substring(0, firstNonWhitespaceCharacter)
+                  + "<OR> "
+                  + atLineString.substring(firstNonWhitespaceCharacter));
         } else {
           strings.add(atLine.toString());
         }
-        previousClazz = atLine.clazz;
+        shouldPrintOr = true;
       }
     }
   }
@@ -84,12 +89,17 @@
   private final RetraceBase retraceBase;
   private final List<String> stackTrace;
   private final DiagnosticsHandler diagnosticsHandler;
+  private final boolean verbose;
 
   RetraceStackTrace(
-      RetraceBase retraceBase, List<String> stackTrace, DiagnosticsHandler diagnosticsHandler) {
+      RetraceBase retraceBase,
+      List<String> stackTrace,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean verbose) {
     this.retraceBase = retraceBase;
     this.stackTrace = stackTrace;
     this.diagnosticsHandler = diagnosticsHandler;
+    this.verbose = verbose;
   }
 
   public RetraceCommandLineResult retrace() {
@@ -107,7 +117,7 @@
       return;
     }
     StackTraceLine stackTraceLine = parseLine(index + 1, stackTrace.get(index));
-    List<StackTraceLine> retraced = stackTraceLine.retrace(retraceBase);
+    List<StackTraceLine> retraced = stackTraceLine.retrace(retraceBase, verbose);
     StackTraceNode node = new StackTraceNode(retraced);
     result.add(node);
     retraceLine(stackTrace, index + 1, result);
@@ -115,7 +125,7 @@
 
   abstract static class StackTraceLine {
 
-    abstract List<StackTraceLine> retrace(RetraceBase retraceBase);
+    abstract List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose);
 
     static int firstNonWhiteSpaceCharacterFromIndex(String line, int index) {
       return firstFromIndex(line, index, not(Character::isWhitespace));
@@ -215,7 +225,7 @@
     }
 
     @Override
-    List<StackTraceLine> retrace(RetraceBase retraceBase) {
+    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
       List<StackTraceLine> exceptionLines = new ArrayList<>();
       retraceBase
           .retrace(Reference.classFromTypeName(exceptionClass))
@@ -267,6 +277,7 @@
     private final String at;
     private final String clazz;
     private final String method;
+    private final String methodAsString;
     private final String fileName;
     private final int linePosition;
     private final boolean isAmbiguous;
@@ -276,6 +287,7 @@
         String at,
         String clazz,
         String method,
+        String methodAsString,
         String fileName,
         int linePosition,
         boolean isAmbiguous) {
@@ -283,6 +295,7 @@
       this.at = at;
       this.clazz = clazz;
       this.method = method;
+      this.methodAsString = methodAsString;
       this.fileName = fileName;
       this.linePosition = linePosition;
       this.isAmbiguous = isAmbiguous;
@@ -335,11 +348,14 @@
       } else {
         fileName = line.substring(parensStart + 1, parensEnd);
       }
+      String className = line.substring(classStartIndex, methodSeparator);
+      String methodName = line.substring(methodSeparator + 1, parensStart);
       return new AtLine(
           line.substring(0, firstNonWhiteSpace),
           line.substring(firstNonWhiteSpace, classStartIndex),
-          line.substring(classStartIndex, methodSeparator),
-          line.substring(methodSeparator + 1, parensStart),
+          className,
+          methodName,
+          className + "." + methodName,
           fileName,
           position,
           false);
@@ -350,49 +366,38 @@
     }
 
     @Override
-    List<StackTraceLine> retrace(RetraceBase retraceBase) {
+    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
       List<StackTraceLine> lines = new ArrayList<>();
       ClassReference classReference = Reference.classFromTypeName(clazz);
-      RetraceMethodResult retraceMethodResult =
-          retraceBase
-              .retrace(classReference)
-              .lookupMethod(method)
-              .narrowByLine(linePosition)
-              .forEach(
-                  methodElement -> {
-                    MethodReference methodReference = methodElement.getMethodReference();
-                    lines.add(
-                        new AtLine(
-                            startingWhitespace,
-                            at,
-                            methodReference.getHolderClass().getTypeName(),
-                            methodReference.getMethodName(),
-                            retraceBase.retraceSourceFile(
-                                classReference, fileName, methodReference.getHolderClass(), true),
-                            hasLinePosition()
-                                ? methodElement.getOriginalLineNumber(linePosition)
-                                : linePosition,
-                            methodElement.getRetraceMethodResult().isAmbiguous()));
-                  });
+      retraceBase
+          .retrace(classReference)
+          .lookupMethod(method)
+          .narrowByLine(linePosition)
+          .forEach(
+              methodElement -> {
+                MethodReference methodReference = methodElement.getMethodReference();
+                lines.add(
+                    new AtLine(
+                        startingWhitespace,
+                        at,
+                        methodReference.getHolderClass().getTypeName(),
+                        methodReference.getMethodName(),
+                        methodDescriptionFromMethodReference(methodReference, verbose),
+                        retraceBase.retraceSourceFile(
+                            classReference, fileName, methodReference.getHolderClass(), true),
+                        hasLinePosition()
+                            ? methodElement.getOriginalLineNumber(linePosition)
+                            : linePosition,
+                        methodElement.getRetraceMethodResult().isAmbiguous()));
+              });
       return lines;
     }
 
     @Override
     public String toString() {
-      return toString(at, "");
-    }
-
-    protected String toString(String at, String previousClass) {
       StringBuilder sb = new StringBuilder(startingWhitespace);
       sb.append(at);
-      String commonPrefix = Strings.commonPrefix(clazz, previousClass);
-      if (commonPrefix.length() == clazz.length()) {
-        sb.append(Strings.repeat(" ", clazz.length() + 1));
-      } else {
-        sb.append(Strings.padStart(clazz.substring(commonPrefix.length()), clazz.length(), ' '));
-        sb.append(".");
-      }
-      sb.append(method);
+      sb.append(methodAsString);
       sb.append("(");
       sb.append(fileName);
       if (linePosition != NO_POSITION) {
@@ -443,7 +448,7 @@
     }
 
     @Override
-    List<StackTraceLine> retrace(RetraceBase retraceBase) {
+    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
       return ImmutableList.of(new MoreLine(line));
     }
 
@@ -461,7 +466,7 @@
     }
 
     @Override
-    List<StackTraceLine> retrace(RetraceBase retraceBase) {
+    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
       return ImmutableList.of(new UnknownLine(line));
     }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
new file mode 100644
index 0000000..81c2272
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -0,0 +1,38 @@
+// 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 com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
+
+public class RetraceUtils {
+
+  public static String methodDescriptionFromMethodReference(
+      MethodReference methodReference, boolean verbose) {
+    if (!verbose || methodReference.isUnknown()) {
+      return methodReference.getHolderClass().getTypeName() + "." + methodReference.getMethodName();
+    }
+    StringBuilder sb = new StringBuilder();
+    sb.append(
+        methodReference.getReturnType() == null
+            ? "void"
+            : methodReference.getReturnType().getTypeName());
+    sb.append(" ");
+    sb.append(methodReference.getHolderClass().getTypeName());
+    sb.append(".");
+    sb.append(methodReference.getMethodName());
+    sb.append("(");
+    boolean seenFirstIndex = false;
+    for (TypeReference formalType : methodReference.getFormalTypes()) {
+      if (seenFirstIndex) {
+        sb.append(",");
+      }
+      seenFirstIndex = true;
+      sb.append(formalType.getTypeName());
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+}
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 25a7b15..9b5029f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.utils.CollectionUtils;
@@ -979,7 +978,7 @@
 
   private DexEncodedMethod validateSingleVirtualTarget(
       DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
-    assert SingleResolutionResult.isValidVirtualTarget(options(), resolutionResult);
+    assert resolutionResult.isVirtualMethod();
 
     if (singleTarget == null || singleTarget == DexEncodedMethod.SENTINEL) {
       return null;
@@ -996,7 +995,7 @@
 
   private boolean isInvalidSingleVirtualTarget(
       DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
-    assert SingleResolutionResult.isValidVirtualTarget(options(), resolutionResult);
+    assert resolutionResult.isVirtualMethod();
     // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception
     // at runtime.
     return !singleTarget.accessFlags.isAtLeastAsVisibleAs(resolutionResult.accessFlags);
@@ -1039,11 +1038,10 @@
     // from the runtime type of the receiver.
     if (receiverLowerBoundType != null) {
       if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
-        if (resolutionResult.isSingleResolution()
-            && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+        if (resolutionResult.isSingleResolution() && resolutionResult.isVirtualTarget()) {
           ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
           if (refinedResolutionResult.isSingleResolution()
-              && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+              && refinedResolutionResult.isVirtualTarget()) {
             return validateSingleVirtualTarget(
                 refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
           }
@@ -1081,7 +1079,7 @@
     // First get the target for the holder type.
     ResolutionResult topMethod = resolveMethodOnClass(holder, method);
     // We might hit none or multiple targets. Both make this fail at runtime.
-    if (!topMethod.isSingleResolution() || !topMethod.isValidVirtualTarget(options())) {
+    if (!topMethod.isSingleResolution() || !topMethod.isVirtualTarget()) {
       method.setSingleVirtualMethodCache(refinedReceiverType, null);
       return null;
     }
@@ -1223,11 +1221,10 @@
     if (receiverLowerBoundType != null) {
       if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
         ResolutionResult resolutionResult = resolveMethod(method.holder, method, true);
-        if (resolutionResult.isSingleResolution()
-            && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+        if (resolutionResult.isSingleResolution() && resolutionResult.isVirtualTarget()) {
           ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
           if (refinedResolutionResult.isSingleResolution()
-              && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+              && refinedResolutionResult.isVirtualTarget()) {
             return validateSingleVirtualTarget(
                 refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
           }
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 7c78ac5..f05d068 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -12,100 +12,107 @@
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 
 public class DefaultEnqueuerUseRegistry extends UseRegistry {
 
-  private final DexProgramClass currentHolder;
-  protected final DexEncodedMethod currentMethod;
+  private final ProgramMethod context;
   private final Enqueuer enqueuer;
 
   public DefaultEnqueuerUseRegistry(
-      AppView<?> appView,
-      DexProgramClass currentHolder,
-      DexEncodedMethod currentMethod,
-      Enqueuer enqueuer) {
+      AppView<?> appView, DexProgramClass holder, DexEncodedMethod method, Enqueuer enqueuer) {
     super(appView.dexItemFactory());
-    assert currentHolder.type == currentMethod.method.holder;
-    this.currentHolder = currentHolder;
-    this.currentMethod = currentMethod;
+    this.context = new ProgramMethod(holder, method);
     this.enqueuer = enqueuer;
   }
 
+  public ProgramMethod getContext() {
+    return context;
+  }
+
+  public DexProgramClass getContextHolder() {
+    return context.holder;
+  }
+
+  public DexEncodedMethod getContextMethod() {
+    return context.method;
+  }
+
   @Override
   public boolean registerInvokeVirtual(DexMethod invokedMethod) {
-    return enqueuer.traceInvokeVirtual(invokedMethod, currentHolder, currentMethod);
+    return enqueuer.traceInvokeVirtual(invokedMethod, context);
   }
 
   @Override
   public boolean registerInvokeDirect(DexMethod invokedMethod) {
-    return enqueuer.traceInvokeDirect(invokedMethod, currentHolder, currentMethod);
+    return enqueuer.traceInvokeDirect(invokedMethod, context);
   }
 
   @Override
   public boolean registerInvokeStatic(DexMethod invokedMethod) {
-    return enqueuer.traceInvokeStatic(invokedMethod, currentHolder, currentMethod);
+    return enqueuer.traceInvokeStatic(invokedMethod, context);
   }
 
   @Override
   public boolean registerInvokeInterface(DexMethod invokedMethod) {
-    return enqueuer.traceInvokeInterface(invokedMethod, currentHolder, currentMethod);
+    return enqueuer.traceInvokeInterface(invokedMethod, context);
   }
 
   @Override
   public boolean registerInvokeSuper(DexMethod invokedMethod) {
-    return enqueuer.traceInvokeSuper(invokedMethod, currentHolder, currentMethod);
+    return enqueuer.traceInvokeSuper(invokedMethod, context);
   }
 
   @Override
   public boolean registerInstanceFieldWrite(DexField field) {
-    return enqueuer.traceInstanceFieldWrite(field, currentMethod);
+    return enqueuer.traceInstanceFieldWrite(field, context.method);
   }
 
   @Override
   public boolean registerInstanceFieldRead(DexField field) {
-    return enqueuer.traceInstanceFieldRead(field, currentMethod);
+    return enqueuer.traceInstanceFieldRead(field, context.method);
   }
 
   @Override
   public boolean registerNewInstance(DexType type) {
-    return enqueuer.traceNewInstance(type, currentMethod);
+    return enqueuer.traceNewInstance(type, context);
   }
 
   @Override
   public boolean registerStaticFieldRead(DexField field) {
-    return enqueuer.traceStaticFieldRead(field, currentMethod);
+    return enqueuer.traceStaticFieldRead(field, context.method);
   }
 
   @Override
   public boolean registerStaticFieldWrite(DexField field) {
-    return enqueuer.traceStaticFieldWrite(field, currentMethod);
+    return enqueuer.traceStaticFieldWrite(field, context.method);
   }
 
   @Override
   public boolean registerConstClass(DexType type) {
-    return enqueuer.traceConstClass(type, currentMethod);
+    return enqueuer.traceConstClass(type, context.method);
   }
 
   @Override
   public boolean registerCheckCast(DexType type) {
-    return enqueuer.traceCheckCast(type, currentMethod);
+    return enqueuer.traceCheckCast(type, context.method);
   }
 
   @Override
   public boolean registerTypeReference(DexType type) {
-    return enqueuer.traceTypeReference(type, currentMethod);
+    return enqueuer.traceTypeReference(type, context.method);
   }
 
   @Override
   public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
     super.registerMethodHandle(methodHandle, use);
-    enqueuer.traceMethodHandle(methodHandle, use, currentMethod);
+    enqueuer.traceMethodHandle(methodHandle, use, context.method);
   }
 
   @Override
   public void registerCallSite(DexCallSite callSite) {
     super.registerCallSite(callSite);
-    enqueuer.traceCallSite(callSite, currentMethod);
+    enqueuer.traceCallSite(callSite, context);
   }
 }
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 6bd3db2..ac1dea1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Descriptor;
@@ -37,6 +38,7 @@
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.FailedResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
@@ -93,7 +95,6 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
-import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -586,21 +587,20 @@
     return isRead ? info.recordRead(field, context) : info.recordWrite(field, context);
   }
 
-  void traceCallSite(DexCallSite callSite, DexEncodedMethod currentMethod) {
+  void traceCallSite(DexCallSite callSite, ProgramMethod context) {
     callSites.add(callSite);
 
     List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
     if (directInterfaces != null) {
       for (DexType lambdaInstantiatedInterface : directInterfaces) {
-        markLambdaInstantiated(lambdaInstantiatedInterface, currentMethod);
+        markLambdaInstantiated(lambdaInstantiatedInterface, context.method);
       }
     } else {
       if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
         if (options.reporter != null) {
           Diagnostic message =
               new StringDiagnostic(
-                  "Unknown bootstrap method " + callSite.bootstrapMethod,
-                  appInfo.originFor(currentMethod.method.holder));
+                  "Unknown bootstrap method " + callSite.bootstrapMethod, context.holder.origin);
           options.reporter.warning(message);
         }
       }
@@ -634,19 +634,19 @@
 
     switch (implHandle.type) {
       case INVOKE_STATIC:
-        traceInvokeStaticFromLambda(method, currentMethod);
+        traceInvokeStaticFromLambda(method, context);
         break;
       case INVOKE_INTERFACE:
-        traceInvokeInterfaceFromLambda(method, currentMethod);
+        traceInvokeInterfaceFromLambda(method, context);
         break;
       case INVOKE_INSTANCE:
-        traceInvokeVirtualFromLambda(method, currentMethod);
+        traceInvokeVirtualFromLambda(method, context);
         break;
       case INVOKE_DIRECT:
-        traceInvokeDirectFromLambda(method, currentMethod);
+        traceInvokeDirectFromLambda(method, context);
         break;
       case INVOKE_CONSTRUCTOR:
-        traceNewInstanceFromLambda(method.holder, currentMethod);
+        traceNewInstanceFromLambda(method.holder, context);
         break;
       default:
         throw new Unreachable();
@@ -726,7 +726,7 @@
       if (clazz != null) {
         KeepReason reason = KeepReason.methodHandleReferencedIn(currentMethod);
         if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
-          markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
+          markInterfaceAsInstantiated(clazz, graphReporter.registerInterface(clazz, reason));
         } else {
           markInstantiated(clazz, null, reason);
         }
@@ -739,8 +739,9 @@
     return true;
   }
 
-  boolean traceInvokeDirect(
-      DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+  boolean traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
+    DexProgramClass currentHolder = context.holder;
+    DexEncodedMethod currentMethod = context.method;
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
             invokedMethod.holder,
@@ -753,7 +754,7 @@
     }
 
     return traceInvokeDirect(
-        invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
+        invokedMethod, context, KeepReason.invokedFrom(currentHolder, currentMethod));
   }
 
   /** Returns true if a deferred action was registered. */
@@ -770,13 +771,14 @@
     return false;
   }
 
-  boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
+  boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeDirect(
-        invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
   }
 
   private boolean traceInvokeDirect(
-      DexMethod invokedMethod, DexEncodedMethod currentMethod, KeepReason reason) {
+      DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
+    DexEncodedMethod currentMethod = context.method;
     if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, currentMethod)) {
       return false;
     }
@@ -787,42 +789,42 @@
     return true;
   }
 
-  boolean traceInvokeInterface(
-      DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+  boolean traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeInterface(
-        invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
+        invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method));
   }
 
-  boolean traceInvokeInterfaceFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
+  boolean traceInvokeInterfaceFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeInterface(
-        invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
   }
 
   private boolean traceInvokeInterface(
-      DexMethod method, DexEncodedMethod currentMethod, KeepReason keepReason) {
+      DexMethod method, ProgramMethod context, KeepReason keepReason) {
+    DexEncodedMethod currentMethod = context.method;
     if (!registerMethodWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
       return false;
     }
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Register invokeInterface `%s`.", method);
     }
-    workList.enqueueMarkReachableInterfaceAction(method, keepReason);
+    markVirtualMethodAsReachable(method, true, context, keepReason);
     return true;
   }
 
-  boolean traceInvokeStatic(
-      DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+  boolean traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeStatic(
-        invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
+        invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method));
   }
 
-  boolean traceInvokeStaticFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
+  boolean traceInvokeStaticFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeStatic(
-        invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
   }
 
   private boolean traceInvokeStatic(
-      DexMethod invokedMethod, DexEncodedMethod currentMethod, KeepReason reason) {
+      DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
+    DexEncodedMethod currentMethod = context.method;
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
         || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
@@ -852,8 +854,9 @@
     return true;
   }
 
-  boolean traceInvokeSuper(
-      DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+  boolean traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
+    DexProgramClass currentHolder = context.holder;
+    DexEncodedMethod currentMethod = context.method;
     // We have to revisit super invokes based on the context they are found in. The same
     // method descriptor will hit different targets, depending on the context it is used in.
     DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, currentMethod);
@@ -867,56 +870,55 @@
     return true;
   }
 
-  boolean traceInvokeVirtual(
-      DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+  boolean traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeVirtual(
-        invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
+        invokedMethod, context, KeepReason.invokedFrom(context.holder, context.method));
   }
 
-  boolean traceInvokeVirtualFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
+  boolean traceInvokeVirtualFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeVirtual(
-        invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
   }
 
   private boolean traceInvokeVirtual(
-      DexMethod invokedMethod, DexEncodedMethod currentMethod, KeepReason reason) {
+      DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
     if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
         || invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
-      pendingReflectiveUses.add(currentMethod);
+      pendingReflectiveUses.add(context.method);
     } else if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(invokedMethod)) {
       // Implicitly add -identifiernamestring rule for the Java reflection in use.
       identifierNameStrings.add(invokedMethod);
       // Revisit the current method to implicitly add -keep rule for items with reflective access.
-      pendingReflectiveUses.add(currentMethod);
+      pendingReflectiveUses.add(context.method);
     }
-    if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, currentMethod)) {
+    if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context.method)) {
       return false;
     }
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Register invokeVirtual `%s`.", invokedMethod);
     }
-    workList.enqueueMarkReachableVirtualAction(invokedMethod, reason);
+    markVirtualMethodAsReachable(invokedMethod, false, context, reason);
     return true;
   }
 
-  boolean traceNewInstance(DexType type, DexEncodedMethod currentMethod) {
+  boolean traceNewInstance(DexType type, ProgramMethod context) {
+    DexEncodedMethod currentMethod = context.method;
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
-            type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, currentMethod));
+            type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, context));
     if (skipTracing) {
       return false;
     }
 
-    return traceNewInstance(type, currentMethod, KeepReason.instantiatedIn(currentMethod));
+    return traceNewInstance(type, context, KeepReason.instantiatedIn(currentMethod));
   }
 
-  boolean traceNewInstanceFromLambda(DexType type, DexEncodedMethod currentMethod) {
-    return traceNewInstance(
-        type, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+  boolean traceNewInstanceFromLambda(DexType type, ProgramMethod context) {
+    return traceNewInstance(type, context, KeepReason.invokedFromLambdaCreatedIn(context.method));
   }
 
-  private boolean traceNewInstance(
-      DexType type, DexEncodedMethod currentMethod, KeepReason keepReason) {
+  private boolean traceNewInstance(DexType type, ProgramMethod context, KeepReason keepReason) {
+    DexEncodedMethod currentMethod = context.method;
     DexProgramClass clazz = getProgramClassOrNull(type);
     if (clazz != null) {
       if (clazz.isInterface()) {
@@ -1416,7 +1418,7 @@
     // It is valid to have an invoke-direct instruction in a default interface method that
     // targets another default method in the same interface (see testInvokeSpecialToDefault-
     // Method). In a class, that would lead to a verification error.
-    if (encodedMethod.isVirtualMethod()
+    if (encodedMethod.isNonPrivateVirtualMethod()
         && virtualMethodsTargetedByInvokeDirect.add(encodedMethod.method)) {
       enqueueMarkMethodLiveAction(clazz, encodedMethod, reason);
     }
@@ -1658,7 +1660,7 @@
   }
 
   private void markResolutionAsLive(DexClass libraryClass, ResolutionResult resolution) {
-    if (resolution.isValidVirtualTarget(options)) {
+    if (resolution.isVirtualTarget()) {
       DexEncodedMethod target = resolution.getSingleTarget();
       DexProgramClass targetHolder = getProgramClassOrNull(target.method.holder);
       if (targetHolder != null
@@ -1960,16 +1962,8 @@
     }
   }
 
-  // Package protected due to entry point from worklist.
-  void markVirtualMethodAsReachable(DexMethod method, boolean interfaceInvoke, KeepReason reason) {
-    markVirtualMethodAsReachable(method, interfaceInvoke, reason, (x, y) -> true);
-  }
-
   private void markVirtualMethodAsReachable(
-      DexMethod method,
-      boolean interfaceInvoke,
-      KeepReason reason,
-      BiPredicate<DexProgramClass, DexEncodedMethod> possibleTargetsFilter) {
+      DexMethod method, boolean interfaceInvoke, ProgramMethod contextOrNull, KeepReason reason) {
     if (method.holder.isArrayType()) {
       // This is an array type, so the actual class will be generated at runtime. We treat this
       // like an invoke on a direct subtype of java.lang.Object that has no further subtypes.
@@ -2002,9 +1996,18 @@
 
     // Otherwise, the resolution target is marked and cached, and all possible targets identified.
     resolution = findAndMarkResolutionTarget(method, interfaceInvoke, reason);
+    if (contextOrNull != null
+        && !resolution.isUnresolved()
+        && !AccessControl.isMethodAccessible(
+            resolution.method, holder, contextOrNull.holder, appInfo)) {
+      // Not accessible from this context, so this call will cause a runtime exception.
+      // Note that the resolution is not cached, as another call context may be valid.
+      return;
+    }
+
+    // The resolution is unresolved or accessible, both are context independent, so cache it.
     virtualTargetsMarkedAsReachable.put(method, resolution);
-    if (resolution.isUnresolved()
-        || !SingleResolutionResult.isValidVirtualTarget(options, resolution.method)) {
+    if (resolution.isUnresolved() || !resolution.method.isVirtualMethod()) {
       // There is no valid resolution, so any call will lead to a runtime exception.
       return;
     }
@@ -2027,24 +2030,20 @@
       if (encodedPossibleTarget.isAbstract()) {
         continue;
       }
-      markPossibleTargetsAsReachable(resolution, possibleTargetsFilter, encodedPossibleTarget);
+      markPossibleTargetsAsReachable(resolution, encodedPossibleTarget);
     }
   }
 
   private void markPossibleTargetsAsReachable(
       MarkedResolutionTarget reason,
-      BiPredicate<DexProgramClass, DexEncodedMethod> possibleTargetsFilter,
       DexEncodedMethod encodedPossibleTarget) {
-    assert encodedPossibleTarget.isVirtualMethod() || options.canUseNestBasedAccess();
+    assert encodedPossibleTarget.isVirtualMethod();
     assert !encodedPossibleTarget.isAbstract();
     DexMethod possibleTarget = encodedPossibleTarget.method;
     DexProgramClass clazz = getProgramClassOrNull(possibleTarget.holder);
     if (clazz == null) {
       return;
     }
-    if (!possibleTargetsFilter.test(clazz, encodedPossibleTarget)) {
-      return;
-    }
     ReachableVirtualMethodsSet reachable =
         reachableVirtualMethods.computeIfAbsent(clazz, ignore -> new ReachableVirtualMethodsSet());
     if (!reachable.add(encodedPossibleTarget, reason)) {
@@ -2500,10 +2499,8 @@
       // A virtual method. Mark it as reachable so that subclasses, if instantiated, keep
       // their overrides. However, we don't mark it live, as a keep rule might not imply that
       // the corresponding class is live.
-      if (!holder.isInterface()) {
-        workList.enqueueMarkReachableVirtualAction(method, reason);
-      } else {
-        workList.enqueueMarkReachableInterfaceAction(method, reason);
+      markVirtualMethodAsReachable(method, holder.isInterface(), null, reason);
+      if (holder.isInterface()) {
         // Reachability for default methods is based on live subtypes in general. For keep rules,
         // we need special handling as we essentially might have live subtypes that are outside of
         // the current compilation unit. Keep either the default-method or its implementation
@@ -2543,7 +2540,7 @@
       DexProgramClass clazz, DexEncodedMethod method) {
     assert method.isVirtualMethod();
 
-    if (method.isAbstract()) {
+    if (method.isAbstract() || method.isPrivateMethod()) {
       return false;
     }
 
@@ -2719,7 +2716,9 @@
       if (clazz == null) {
         return;
       }
-      if (!clazz.isInterface()) {
+      if (clazz.isInterface()) {
+        markTypeAsLive(clazz.type, KeepReason.reflectiveUseIn(method));
+      } else {
         markInstantiated(clazz, null, KeepReason.reflectiveUseIn(method));
         if (clazz.hasDefaultInitializer()) {
           DexEncodedMethod initializer = clazz.getDefaultInitializer();
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index ff6ece0..d98d7c9 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -11,6 +11,7 @@
 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.ProgramMethod;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import java.util.ArrayDeque;
 import java.util.Queue;
@@ -36,36 +37,6 @@
     }
   }
 
-  static class MarkReachableVirtualAction extends EnqueuerAction {
-    final DexMethod target;
-    final KeepReason reason;
-
-    MarkReachableVirtualAction(DexMethod target, KeepReason reason) {
-      this.target = target;
-      this.reason = reason;
-    }
-
-    @Override
-    public void run(Enqueuer enqueuer) {
-      enqueuer.markVirtualMethodAsReachable(target, false, reason);
-    }
-  }
-
-  static class MarkReachableInterfaceAction extends EnqueuerAction {
-    final DexMethod target;
-    final KeepReason reason;
-
-    public MarkReachableInterfaceAction(DexMethod target, KeepReason reason) {
-      this.target = target;
-      this.reason = reason;
-    }
-
-    @Override
-    public void run(Enqueuer enqueuer) {
-      enqueuer.markVirtualMethodAsReachable(target, true, reason);
-    }
-  }
-
   static class MarkReachableSuperAction extends EnqueuerAction {
     final DexMethod target;
     final DexEncodedMethod context;
@@ -194,22 +165,22 @@
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.traceInvokeDirect(invokedMethod, currentHolder, currentMethod);
+      enqueuer.traceInvokeDirect(invokedMethod, new ProgramMethod(currentHolder, currentMethod));
     }
   }
 
   static class TraceNewInstanceAction extends EnqueuerAction {
     final DexType type;
-    final DexEncodedMethod currentMethod;
+    final ProgramMethod context;
 
-    TraceNewInstanceAction(DexType type, DexEncodedMethod currentMethod) {
+    TraceNewInstanceAction(DexType type, ProgramMethod context) {
       this.type = type;
-      this.currentMethod = currentMethod;
+      this.context = context;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.traceNewInstance(type, currentMethod);
+      enqueuer.traceNewInstance(type, context);
     }
   }
 
@@ -251,14 +222,6 @@
     queue.add(new MarkReachableDirectAction(method, reason));
   }
 
-  void enqueueMarkReachableVirtualAction(DexMethod method, KeepReason reason) {
-    queue.add(new MarkReachableVirtualAction(method, reason));
-  }
-
-  void enqueueMarkReachableInterfaceAction(DexMethod method, KeepReason reason) {
-    queue.add(new MarkReachableInterfaceAction(method, reason));
-  }
-
   void enqueueMarkReachableSuperAction(DexMethod method, DexEncodedMethod from) {
     queue.add(new MarkReachableSuperAction(method, from));
   }
@@ -306,9 +269,8 @@
     queue.add(new TraceInvokeDirectAction(invokedMethod, currentHolder, currentMethod));
   }
 
-  public void enqueueTraceNewInstanceAction(DexType type, DexEncodedMethod currentMethod) {
-    assert currentMethod.isProgramMethod(appView);
-    queue.add(new TraceNewInstanceAction(type, currentMethod));
+  public void enqueueTraceNewInstanceAction(DexType type, ProgramMethod context) {
+    queue.add(new TraceNewInstanceAction(type, context));
   }
 
   public void enqueueTraceStaticFieldRead(DexField field, DexEncodedMethod currentMethod) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index b86857a..b842680 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -533,8 +533,7 @@
       }
       ResolutionResult resolutionResult =
           appView.appInfo().resolveMethod(originalClazz, method.method);
-      if (!resolutionResult.isValidVirtualTarget(appView.options())
-          || !resolutionResult.isSingleResolution()) {
+      if (!resolutionResult.isVirtualTarget() || !resolutionResult.isSingleResolution()) {
         return;
       }
       DexEncodedMethod methodToKeep = resolutionResult.getSingleTarget();
diff --git a/src/main/java/com/android/tools/r8/utils/AssertionConfigurationWithDefault.java b/src/main/java/com/android/tools/r8/utils/AssertionConfigurationWithDefault.java
new file mode 100644
index 0000000..7dc7b33
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/AssertionConfigurationWithDefault.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformationScope;
+import java.util.List;
+
+public class AssertionConfigurationWithDefault {
+
+  public final AssertionTransformation defautlTransformation;
+  public final List<AssertionsConfiguration> assertionsConfigurations;
+
+  public AssertionConfigurationWithDefault(
+      AssertionTransformation defautlTransformation,
+      List<AssertionsConfiguration> assertionsConfigurations) {
+    this.defautlTransformation = defautlTransformation;
+    assert assertionsConfigurations != null;
+    this.assertionsConfigurations = assertionsConfigurations;
+  }
+
+  public boolean isPassthroughAll() {
+    if (assertionsConfigurations.size() == 0) {
+      return defautlTransformation == AssertionTransformation.PASSTHROUGH;
+    }
+    return assertionsConfigurations.size() == 1
+        && assertionsConfigurations.get(0).getScope() == AssertionTransformationScope.ALL
+        && assertionsConfigurations.get(0).getTransformation()
+            == AssertionTransformation.PASSTHROUGH;
+  }
+}
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 dccd176..fd51973 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -329,6 +329,10 @@
     return className.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR);
   }
 
+  public static String getJavaTypeFromBinaryName(String className) {
+    return className.replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
+  }
+
   public static String getBinaryNameFromDescriptor(String classDescriptor) {
     assert isClassDescriptor(classDescriptor);
     return classDescriptor.substring(1, classDescriptor.length() - 1);
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 9b47305..1425bc0 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,6 @@
 
 import static com.google.common.base.Predicates.not;
 
-import com.android.tools.r8.AssertionsConfiguration;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DataResourceConsumer;
@@ -243,6 +242,7 @@
   // TODO(b/125282093): Enable member value propagation for instance fields.
   public boolean enableValuePropagationForInstanceFields = false;
   public boolean enableUninstantiatedTypeOptimization = true;
+  public boolean enableUninstantiatedTypeOptimizationForInterfaces = true;
   // TODO(b/138917494): Disable until we have numbers on potential performance penalties.
   public boolean enableRedundantConstNumberOptimization = false;
 
@@ -366,6 +366,9 @@
         }
       }
     }
+    if (desugarGraphConsumer != null) {
+      desugarGraphConsumer.finished();
+    }
   }
 
   public boolean shouldDesugarNests() {
@@ -434,7 +437,7 @@
   public boolean ignoreMissingClasses = false;
   // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
   public boolean forceProguardCompatibility = false;
-  public AssertionsConfiguration assertionsConfiguration = null;
+  public AssertionConfigurationWithDefault assertionsConfiguration = null;
   public boolean configurationDebugging = false;
 
   // Don't convert Code objects to IRCode.
diff --git a/src/main/java/com/android/tools/r8/utils/SelfRetraceTest.java b/src/main/java/com/android/tools/r8/utils/SelfRetraceTest.java
index ab891e8..dc7ce2b 100644
--- a/src/main/java/com/android/tools/r8/utils/SelfRetraceTest.java
+++ b/src/main/java/com/android/tools/r8/utils/SelfRetraceTest.java
@@ -22,7 +22,7 @@
   }
 
   public static void test() {
-    if (System.getenv(SELFRETRACETEST_ENVIRONMENT_VAR) != null) {
+    if (System.getProperty(SELFRETRACETEST_ENVIRONMENT_VAR) != null) {
       new SelfRetraceTest().foo1();
     }
   }
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
index 23dc7e0..41b328c 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
@@ -335,6 +336,7 @@
               .addLibraryFiles(libraries)
               .addProgramFiles(inputs)
               .setDisableDesugaring(false)
+              .setDesugarGraphConsumer(new MyDesugarGraphConsumer())
               .build());
     } catch (CompilationFailedException e) {
       throw new RuntimeException("Unexpected compilation exceptions", e);
@@ -498,4 +500,15 @@
       }
     }
   }
+
+  private static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
+
+    @Override
+    public void accept(Origin dependent, Origin dependency) {
+    }
+
+    public void finished() {
+
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index ae63272..db0baa3 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -525,6 +525,13 @@
         "Missing parameter", handler -> parse(handler, "--output"));
   }
 
+  @Test
+  public void desugaredLibrary() throws CompilationFailedException {
+    D8Command d8Command = parse("--desugared-lib", "src/library_desugar/desugar_jdk_libs.json");
+    assertFalse(
+        d8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
+  }
+
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 0a55b8a..558ba0d 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -4,10 +4,12 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.charset.StandardCharsets;
@@ -68,6 +70,27 @@
     Marker marker = markers.iterator().next();
   }
 
+  @Test
+  public void testMarkerCommandLine() throws Throwable {
+    Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
+    L8Command l8Command =
+        parse(
+            ToolHelper.getDesugarJDKLibs().toString(),
+            "--lib",
+            ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+            "--min-api",
+            "20",
+            "--desugared-lib",
+            ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(),
+            "--output",
+            output.toString());
+    L8.run(l8Command);
+    Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+    // TODO(b/134732760): Shouldn't we remove the D8/R8 marker?
+    assertEquals(2, markers.size());
+    Marker marker = markers.iterator().next();
+  }
+
   private L8Command.Builder prepareBuilder(DiagnosticsHandler handler) {
     return L8Command.builder(handler)
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
@@ -156,4 +179,15 @@
     assertTrue(builder2.isShrinking());
     assertNotNull(builder2.build().getR8Command());
   }
+
+  @Test
+  public void desugaredLibrary() throws CompilationFailedException {
+    L8Command l8Command = parse("--desugared-lib", "src/library_desugar/desugar_jdk_libs.json");
+    assertFalse(
+        l8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
+  }
+
+  private L8Command parse(String... args) throws CompilationFailedException {
+    return L8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 57d278e..165b510 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -206,7 +206,8 @@
     Path mainDexRules1 = temp.newFile("main-dex-1.rules").toPath();
     Path mainDexRules2 = temp.newFile("main-dex-2.rules").toPath();
     parse("--main-dex-rules", mainDexRules1.toString());
-    parse("--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
+    parse(
+        "--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
   }
 
   @Test(expected = CompilationFailedException.class)
@@ -637,6 +638,13 @@
         "Missing parameter", handler -> parse(handler, "--output"));
   }
 
+  @Test
+  public void desugaredLibrary() throws CompilationFailedException {
+    R8Command r8Command = parse("--desugared-lib", "src/library_desugar/desugar_jdk_libs.json");
+    assertFalse(
+        r8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
+  }
+
   private R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index a19cfec..0ba8510 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -5,8 +5,12 @@
 
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.google.common.io.ByteStreams.toByteArray;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.CompilationFailedException;
@@ -18,8 +22,12 @@
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.retrace.Retrace;
+import com.android.tools.r8.retrace.RetraceCommand;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Lists;
 import java.io.BufferedInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -104,14 +112,59 @@
 
   @Test
   public void testRetrace() throws IOException {
-    ProcessResult result =
+    ProcessResult processResult =
         ToolHelper.runProcess(
-            new ProcessBuilder(
-                "python",
-                Paths.get(ToolHelper.TOOLS_DIR, "test_self_retrace.py").toString(),
-                r8R8Release.getFirst().toString(),
-                r8R8Release.getSecond().toString()));
-    assertEquals(result.toString(), 0, result.exitCode);
+            new ProcessBuilder()
+                .command(
+                    parameters.getRuntime().asCf().getJavaExecutable().toString(),
+                    "-DR8_THROW_EXCEPTION_FOR_TESTING_RETRACE=1",
+                    "-cp",
+                    r8R8Release.getFirst().toString(),
+                    "com.android.tools.r8.R8",
+                    "--help"));
+    assertNotEquals(0, processResult.exitCode);
+    assertThat(processResult.stderr, not(containsString("SelfRetraceTest")));
+
+    List<String> expectedStackTrace =
+        Lists.newArrayList(
+            "Intentional exception for testing retrace.",
+            "com.android.tools.r8.utils.SelfRetraceTest.foo3(SelfRetraceTest.java:13)",
+            "com.android.tools.r8.utils.SelfRetraceTest.foo2(SelfRetraceTest.java:17)",
+            "com.android.tools.r8.utils.SelfRetraceTest.foo1(SelfRetraceTest.java:21)",
+            "com.android.tools.r8.utils.SelfRetraceTest.test(SelfRetraceTest.java:26)",
+            "com.android.tools.r8.R8.run(R8.java:");
+
+    RetraceCommand retraceCommand =
+        RetraceCommand.builder()
+            .setStackTrace(StringUtils.splitLines(processResult.stderr))
+            .setProguardMapProducer(
+                () -> {
+                  Path mappingFile = r8R8Release.getSecond();
+                  try {
+                    return new String(Files.readAllBytes(mappingFile));
+                  } catch (IOException e) {
+                    e.printStackTrace();
+                    throw new RuntimeException(
+                        "Could not read mapping file " + mappingFile.toString());
+                  }
+                })
+            .setRetracedStackTraceConsumer(
+                retraced -> {
+                  int expectedIndex = -1;
+                  for (String line : retraced) {
+                    if (expectedIndex >= expectedStackTrace.size()) {
+                      break;
+                    } else if (expectedIndex == -1 && line.contains("java.lang.RuntimeException")) {
+                      expectedIndex = 0;
+                    }
+                    if (expectedIndex > -1) {
+                      assertThat(line, containsString(expectedStackTrace.get(expectedIndex++)));
+                    }
+                  }
+                  assertEquals(expectedStackTrace.size(), expectedIndex);
+                })
+            .build();
+    Retrace.run(retraceCommand);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index cd3e666..2330854 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -90,11 +90,6 @@
             .addInnerClasses(CustomCollectionTest.class)
             .setMinApi(parameters.getApiLevel())
             .addKeepClassAndMembersRules(Executor.class)
-            .addOptionsModification(
-                options -> {
-                  // TODO(b/140233505): Allow devirtualization once fixed.
-                  options.enableDevirtualization = false;
-                })
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
             .inspect(
@@ -145,22 +140,28 @@
                 instr ->
                     instr.toString().contains("$-EL")
                         || instr.toString().contains("Comparator$-CC")));
-    inherited.streamInstructions().forEach(CustomCollectionTest::assertEmulatedInterfaceDispatch);
+    inherited.streamInstructions().forEach(x -> assertInheritedDispatchCorrect(x, r8));
   }
 
-  private static void assertEmulatedInterfaceDispatch(InstructionSubject instructionSubject) {
+  private void assertInheritedDispatchCorrect(InstructionSubject instructionSubject, boolean r8) {
     if (!instructionSubject.isConstString(JumboStringMode.ALLOW)) {
       for (String s : new String[] {"stream", "parallelStream", "spliterator", "sort"}) {
         if (instructionSubject.toString().contains(s)) {
-          assertTrue(instructionSubject.isInvokeStatic());
-          assertTrue(
-              instructionSubject.toString().contains("$-EL")
-                  || instructionSubject.toString().contains("Comparator$-CC"));
+          if (!r8 || instructionSubject.isInvokeStatic()) {
+            assertTrue(instructionSubject.isInvokeStatic());
+            assertTrue(
+                instructionSubject.toString().contains("$-EL")
+                    || instructionSubject.toString().contains("Comparator$-CC"));
+          } else {
+            // Has been devirtualized.
+            assertTrue(instructionSubject.isInvokeVirtual());
+          }
         }
       }
     }
   }
 
+
   static class Executor {
 
     // In directTypes() the collections use directly their type which implements a j$ interface
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
index 2111b7d..186b01b 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.graph;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.origin.Origin;
 import java.util.Collections;
@@ -13,13 +16,16 @@
 
 public class DesugarGraphTestConsumer implements DesugarGraphConsumer {
 
+  private boolean finished = false;
   private Map<Origin, Set<Origin>> edges = new HashMap<>();
 
   public boolean contains(Origin dependency, Origin dependent) {
+    assertTrue(finished);
     return edges.getOrDefault(dependency, Collections.emptySet()).contains(dependent);
   }
 
   public int totalEdgeCount() {
+    assertTrue(finished);
     int count = 0;
     for (Set<Origin> dependents : edges.values()) {
       count += dependents.size();
@@ -29,6 +35,13 @@
 
   @Override
   public synchronized void accept(Origin dependent, Origin dependency) {
+    assertFalse(finished);
     edges.computeIfAbsent(dependency, s -> new HashSet<>()).add(dependent);
   }
+
+  @Override
+  public void finished() {
+    assertFalse(finished);
+    finished = true;
+  }
 }
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
index af3c6a0..937540e 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphUtils.java
@@ -12,8 +12,12 @@
 
   public static Origin addClassWithOrigin(Class<?> clazz, D8TestBuilder builder)
       throws IOException {
-    Origin origin = makeOrigin(clazz.getTypeName());
-    builder.getBuilder().addClassProgramData(ToolHelper.getClassAsBytes(clazz), origin);
+    return addClassWithOrigin(clazz.getTypeName(), ToolHelper.getClassAsBytes(clazz), builder);
+  }
+
+  public static Origin addClassWithOrigin(String name, byte[] bytes, D8TestBuilder builder) {
+    Origin origin = makeOrigin(name);
+    builder.getBuilder().addClassProgramData(bytes, origin);
     return origin;
   }
 
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 7f76cad..705410a 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
@@ -36,7 +36,7 @@
     // Emtpy.
   }
 
-  public class A implements I {
+  public static class A implements I {
     // Empty.
   }
 
@@ -80,8 +80,8 @@
           .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.totalEdgeCount());
         assertTrue(consumer.contains(originI, originA));
+        assertEquals(1, consumer.totalEdgeCount());
       } else {
         assertEquals(0, consumer.totalEdgeCount());
       }
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/LambdaDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/LambdaDependencyTest.java
new file mode 100644
index 0000000..d7f39e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/LambdaDependencyTest.java
@@ -0,0 +1,81 @@
+// 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.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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LambdaDependencyTest extends TestBase {
+
+  public interface I {
+    void foo();
+  }
+
+  public static class A {
+    void bar(I i) {
+      i.foo();
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      new A().bar(() -> System.out.println("lambda!"));
+    }
+  }
+
+  // Test runner follows.
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public LambdaDependencyTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClasses(I.class, A.class, TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("lambda!");
+    } else {
+      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);
+      Origin originMain = DesugarGraphUtils.addClassWithOrigin(TestClass.class, builder);
+      builder
+          .setMinApi(parameters.getApiLevel())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("lambda!");
+      // If API level indicates desugaring is needed check the edges are reported.
+      if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
+        // Generated lambda class in TestClass.main depends on potential default methods in I.
+        assertTrue(consumer.contains(originI, originMain));
+        assertEquals(1, consumer.totalEdgeCount());
+      } else {
+        assertEquals(0, consumer.totalEdgeCount());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/NestDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/NestDependencyTest.java
new file mode 100644
index 0000000..c67f497
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/NestDependencyTest.java
@@ -0,0 +1,87 @@
+// 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.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.TestRuntime.CfVm;
+import com.android.tools.r8.origin.Origin;
+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 NestDependencyTest extends TestBase {
+
+  public static class Host {}
+
+  public static class Member1 {}
+
+  public static class Member2 {}
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello World!");
+    }
+  }
+
+  // Test runner follows.
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .build();
+  }
+
+  private final TestParameters parameters;
+
+  public NestDependencyTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private byte[] inNest(Class<?> clazz) throws Exception {
+    return transformer(clazz).setNest(Host.class, Member1.class, Member2.class).transform();
+  }
+
+  @Test
+  public void test() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(inNest(Host.class), inNest(Member1.class), inNest(Member2.class))
+          .addProgramClasses(TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello World!");
+    } else {
+      D8TestBuilder builder = testForD8();
+      DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+      builder.getBuilder().setDesugarGraphConsumer(consumer);
+      Origin originHost = DesugarGraphUtils.addClassWithOrigin("Host", inNest(Host.class), builder);
+      Origin originMember1 =
+          DesugarGraphUtils.addClassWithOrigin("Member1", inNest(Member1.class), builder);
+      Origin originMember2 =
+          DesugarGraphUtils.addClassWithOrigin("Member2", inNest(Member2.class), builder);
+      builder
+          .addProgramClasses(TestClass.class)
+          .setMinApi(parameters.getApiLevel())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello World!");
+      // Currently there is no level at which nest desugaring is not needed.
+      assertTrue(consumer.contains(originHost, originMember1));
+      assertTrue(consumer.contains(originHost, originMember2));
+      assertTrue(consumer.contains(originMember1, originHost));
+      assertTrue(consumer.contains(originMember2, originHost));
+      assertEquals(4, consumer.totalEdgeCount());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index aa52d82..dbdc654 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -30,7 +30,11 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimesStartingFromIncluding(Version.V5_1_1).build();
+    return getTestParameters()
+        // Use of APIs, such as java.util.functions.* are only available from 24+
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .withDexRuntimes()
+        .build();
   }
 
   private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
@@ -42,6 +46,7 @@
   @Test
   public void testR8CompiledWithR8() throws Exception {
     testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR_11)
         .addKeepRuleFiles(MAIN_KEEP)
         .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
diff --git a/src/test/java/com/android/tools/r8/dexfilemerger/NonAsciiClassNameChecksumTest.java b/src/test/java/com/android/tools/r8/dexfilemerger/NonAsciiClassNameChecksumTest.java
new file mode 100644
index 0000000..8e4700b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexfilemerger/NonAsciiClassNameChecksumTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.dexfilemerger;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NonAsciiClassNameChecksumTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello æ");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public NonAsciiClassNameChecksumTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private String getTransformedName(Class<?> clazz) {
+    return getTransformedName(clazz.getTypeName());
+  }
+
+  private String getTransformedName(String typeName) {
+    return NonAsciiClassNameChecksumTest.class.getTypeName()
+        + "$"
+        + (typeName.equals(TestClaass.class.getTypeName()) ? "TestClåss" : "TæstClass");
+  }
+
+  private byte[] getTransform(Class<?> clazz) throws IOException {
+    return transformer(clazz)
+        .setClassDescriptor(DescriptorUtils.javaTypeToDescriptor(getTransformedName(clazz)))
+        .transform();
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path intermediate1 = compileIntermediate(TaestClass.class);
+    Path intermediate2 = compileIntermediate(TestClaass.class);
+    testForD8()
+        .addProgramFiles(intermediate1, intermediate2)
+        .setMinApi(parameters.getApiLevel())
+        .setIncludeClassesChecksum(true)
+        .run(parameters.getRuntime(), getTransformedName(TaestClass.class))
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(inspector -> {
+          checkIncludesChecksum(inspector, TaestClass.class);
+          checkIncludesChecksum(inspector, TestClaass.class);
+        });
+  }
+
+  private Path compileIntermediate(Class<?> clazz) throws Exception {
+    return testForD8()
+        .setOutputMode(OutputMode.DexFilePerClassFile)
+        .addProgramClassFileData(getTransform(clazz))
+        .setMinApi(parameters.getApiLevel())
+        .setIncludeClassesChecksum(true)
+        .compile()
+        .inspect(inspector -> checkIncludesChecksum(inspector, clazz))
+        .writeToZip();
+  }
+
+  private void checkIncludesChecksum(CodeInspector inspector, Class<?> clazz) {
+    ClassSubject classSubject = inspector.clazz(getTransformedName(clazz));
+    assertThat(classSubject, isPresent());
+    assertTrue(classSubject.getDexClass().asProgramClass().getChecksum() > 0);
+  }
+
+  static class TaestClass {
+    public static void main(String[] args) {
+      System.out.println("Hello æ");
+    }
+  }
+
+  static class TestClaass {
+    public static void main(String[] args) {
+      System.out.println("Hello å");
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameInterfaceTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameInterfaceTest.java
new file mode 100644
index 0000000..31f8463
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameInterfaceTest.java
@@ -0,0 +1,60 @@
+// 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.reflection;
+
+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 java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 ForNameInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ForNameInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public interface I {}
+
+  public @interface J {}
+
+  public static class Main {
+
+    public static void main(String[] args) throws ClassNotFoundException {
+      Class<?> aClass =
+          Class.forName("com.android.tools.r8.ir.optimize.reflection.ForNameInterfaceTest$I");
+      System.out.println(aClass.getName());
+      aClass = Class.forName("com.android.tools.r8.ir.optimize.reflection.ForNameInterfaceTest$J");
+      System.out.println(aClass.getName());
+    }
+  }
+
+  @Test
+  public void testForNameOnInterface()
+      throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ForNameInterfaceTest.class)
+        .addKeepMainRule(Main.class)
+        .minification(false)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "com.android.tools.r8.ir.optimize.reflection.ForNameInterfaceTest$I",
+            "com.android.tools.r8.ir.optimize.reflection.ForNameInterfaceTest$J");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
new file mode 100644
index 0000000..289e095
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class B146957343 extends TestBase implements Opcodes {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public B146957343(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private byte[] getAimplementsI() throws IOException {
+    return transformer(A.class).setImplements(I.class).transform();
+  }
+
+  @Test
+  public void testWithoutR8() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, J.class, Main.class)
+        .addProgramClassFileData(getAimplementsI())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("In A.f()");
+  }
+
+  @Test
+  public void testWithUninstantiatedTypeOptimizationForInterfaces() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, J.class, Main.class)
+        .addProgramClassFileData(getAimplementsI())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-keep class **A { createA(); }")
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> options.enableUninstantiatedTypeOptimizationForInterfaces = true)
+        .compile()
+        .disassemble()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class);
+  }
+
+  @Test
+  public void testWithoutUninstantiatedTypeOptimizationForInterfaces() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, J.class, Main.class)
+        .addProgramClassFileData(getAimplementsI())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-keep class **A { createA(); }")
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> options.enableUninstantiatedTypeOptimizationForInterfaces = false)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("In A.f()");
+  }
+
+  public interface I {}
+
+  public interface J extends I {}
+
+  public static class A implements J {
+    public static J createA() {
+      return new A();
+    }
+
+    public void f() {
+      System.out.println("In A.f()");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      ((A) A.createA()).f();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/PrivateInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/resolution/PrivateInvokeVirtualTest.java
new file mode 100644
index 0000000..543ecad
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/PrivateInvokeVirtualTest.java
@@ -0,0 +1,95 @@
+// 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.resolution;
+
+import static org.junit.Assert.assertEquals;
+
+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 java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PrivateInvokeVirtualTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PrivateInvokeVirtualTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public static class UInt implements Comparable<UInt> {
+
+    private final int val;
+
+    public UInt(int val) {
+      this.val = val;
+    }
+
+    private int compareToHelper(int i) {
+      return Integer.compare(val, i);
+    }
+
+    @Override
+    public int compareTo(UInt o) {
+      return compareToHelper(o.val);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new UInt(3).compareTo(new UInt(args.length)));
+    }
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getUIntWithTransformedInvoke())
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1");
+  }
+
+  @Test
+  public void testPrivateInvokeVirtual()
+      throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getUIntWithTransformedInvoke())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1");
+  }
+
+  private byte[] getUIntWithTransformedInvoke() throws IOException {
+    return transformer(UInt.class)
+        .transformMethodInsnInMethod(
+            "compareTo",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.contains("compareToHelper")) {
+                assertEquals(Opcodes.INVOKESPECIAL, opcode);
+                continuation.apply(Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface);
+              } else {
+                continuation.apply(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index b1bcb52..e290d51 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -228,7 +228,7 @@
     Assert.assertNotNull(
         appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
-    if (resolutionResult.isValidVirtualTarget(appInfo.app().options)) {
+    if (resolutionResult.isVirtualTarget()) {
       Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualTargets(appInfo);
       Set<DexType> targetHolders =
           targets.stream().map(m -> m.method.holder).collect(Collectors.toSet());
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index 63aa76f..c90b6e4 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -122,7 +122,7 @@
         appInfo.resolveMethodOnInterface(methodOnB.holder, methodOnB);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnB, resolved.method);
-    assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+    assertFalse(resolutionResult.isVirtualTarget());
     DexEncodedMethod singleVirtualTarget =
         appInfo.lookupSingleInterfaceTarget(methodOnB, methodOnB.holder);
     Assert.assertNull(singleVirtualTarget);
@@ -133,7 +133,7 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(methodOnB.holder, methodOnB);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnB, resolved.method);
-    assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+    assertFalse(resolutionResult.isVirtualTarget());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index 22a12f2..f41a60f 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -168,7 +168,7 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.method);
-    assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+    assertFalse(resolutionResult.isVirtualTarget());
     DexEncodedMethod singleVirtualTarget =
         appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
     Assert.assertNull(singleVirtualTarget);
@@ -179,7 +179,7 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.method);
-    assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+    assertFalse(resolutionResult.isVirtualTarget());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
index 69cdad5..f1c5fa1 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
@@ -92,16 +92,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .apply(
-            result -> {
-              if (parameters.isDexRuntime() && inSameNest) {
-                // TODO(b/145187969): R8 incorrectly compiles the nest based access away.
-                result.assertFailureWithErrorThatMatches(
-                    containsString(NullPointerException.class.getName()));
-              } else {
-                checkExpectedResult(result);
-              }
-            });
+        .apply(this::checkExpectedResult);
   }
 
   private void checkExpectedResult(TestRunResult<?> result) {
diff --git a/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
index a997622..f2f3309 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.resolution.access;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
@@ -77,16 +76,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
-        .apply(
-            result -> {
-              if (parameters.isCfRuntime()) {
-                result.assertSuccessWithOutput(EXPECTED);
-              } else {
-                // TODO(b/145187969): R8 compiles an incorrect program.
-                result.assertFailureWithErrorThatMatches(
-                    containsString(NullPointerException.class.getName()));
-              }
-            });
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   static class A {
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithoutNullCheck.java b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithoutNullCheck.java
rename to src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
index 6126c4b..c46d3ce 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithoutNullCheck.java
+++ b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.retrace.stacktraces;
+package com.android.tools.r8.retrace;
 
 import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
@@ -25,7 +25,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class InlineWithoutNullCheck extends TestBase {
+public class InlineWithoutNullCheckTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -34,7 +34,7 @@
     return getTestParameters().withAllRuntimes().withAllApiLevels().build();
   }
 
-  public InlineWithoutNullCheck(TestParameters parameters) {
+  public InlineWithoutNullCheckTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -47,21 +47,21 @@
     // Get the expected stack traces by running on the runtime to test.
     expectedStackTraceForInlineMethod =
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
-            .addInnerClasses(InlineWithoutNullCheck.class)
+            .addInnerClasses(InlineWithoutNullCheckTest.class)
             .run(parameters.getRuntime(), TestClassForInlineMethod.class)
             .writeProcessResult(System.out)
             .assertFailure()
             .getStackTrace();
     expectedStackTraceForInlineField =
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
-            .addInnerClasses(InlineWithoutNullCheck.class)
+            .addInnerClasses(InlineWithoutNullCheckTest.class)
             .run(parameters.getRuntime(), TestClassForInlineField.class)
             .writeProcessResult(System.out)
             .assertFailure()
             .getStackTrace();
     expectedStackTraceForInlineStaticField =
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
-            .addInnerClasses(InlineWithoutNullCheck.class)
+            .addInnerClasses(InlineWithoutNullCheckTest.class)
             .run(parameters.getRuntime(), TestClassForInlineStaticField.class)
             .writeProcessResult(System.out)
             .assertFailure()
@@ -104,7 +104,7 @@
   @Test
   public void testInlineMethodWhichChecksNullReceiverBeforeAnySideEffectMethod() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(InlineWithoutNullCheck.class)
+        .addInnerClasses(InlineWithoutNullCheckTest.class)
         .addKeepMainRule(TestClassForInlineMethod.class)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -132,7 +132,7 @@
   @Test
   public void testInlineMethodWhichChecksNullReceiverBeforeAnySideEffectField() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(InlineWithoutNullCheck.class)
+        .addInnerClasses(InlineWithoutNullCheckTest.class)
         .addKeepMainRule(TestClassForInlineField.class)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -161,7 +161,7 @@
   public void testInlineMethodWhichChecksNullReceiverBeforeAnySideEffectStaticField()
       throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(InlineWithoutNullCheck.class)
+        .addInnerClasses(InlineWithoutNullCheckTest.class)
         .addKeepMainRule(TestClassForInlineStaticField.class)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 258d869..102f7f0 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -4,13 +4,16 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.ToolHelper.LINE_SEPARATOR;
+import static com.android.tools.r8.retrace.RetraceTests.DEFAULT_REGULAR_EXPRESSION;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
+import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTraceWithInfo;
+import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -22,6 +25,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import org.hamcrest.Matcher;
 import org.junit.Rule;
@@ -30,7 +34,7 @@
 
 public class RetraceCommandLineTests {
 
-  private static final boolean testExternal = true;
+  private static final boolean testExternal = false;
 
   @Rule public TemporaryFolder folder = new TemporaryFolder();
 
@@ -56,7 +60,36 @@
 
   @Test
   public void testVerbose() throws IOException {
-    runAbortTest(containsString("Currently no support for --verbose"), "--verbose");
+    FoundMethodVerboseStackTrace stackTrace = new FoundMethodVerboseStackTrace();
+    runTest(
+        stackTrace.mapping(),
+        StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR,
+        "--verbose");
+  }
+
+  @Test
+  public void testRegularExpression() throws IOException {
+    ActualRetraceBotStackTrace stackTrace = new ActualRetraceBotStackTrace();
+    runTest(
+        stackTrace.mapping(),
+        StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR,
+        "--regex=" + DEFAULT_REGULAR_EXPRESSION);
+  }
+
+  @Test
+  public void testRegularExpressionWithInfo() throws IOException {
+    ActualRetraceBotStackTraceWithInfo stackTrace = new ActualRetraceBotStackTraceWithInfo();
+    runTest(
+        stackTrace.mapping(),
+        StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR,
+        "--regex=" + DEFAULT_REGULAR_EXPRESSION,
+        "--info");
   }
 
   @Test
@@ -66,7 +99,7 @@
 
   @Test
   public void testHelp() throws IOException {
-    ProcessResult processResult = runRetraceCommandLine(null, "--help");
+    ProcessResult processResult = runRetraceCommandLine(null, Arrays.asList("--help"));
     assertEquals(0, processResult.exitCode);
     assertEquals(Retrace.USAGE_MESSAGE, processResult.stdout);
   }
@@ -82,34 +115,39 @@
           "    at r8.retrace(App:184)",
           "    ... 7 more");
 
-  private void runTest(String mapping, String stackTrace, boolean stacktraceStdIn, String expected)
+  private void runTest(
+      String mapping, String stackTrace, boolean stacktraceStdIn, String expected, String... args)
       throws IOException {
-    ProcessResult result = runRetrace(mapping, stackTrace, stacktraceStdIn);
+    ProcessResult result = runRetrace(mapping, stackTrace, stacktraceStdIn, args);
     assertEquals(0, result.exitCode);
     assertEquals(expected, result.stdout);
   }
 
   private void runAbortTest(Matcher<String> errorMatch, String... args) throws IOException {
-    ProcessResult result = runRetraceCommandLine(null, args);
+    ProcessResult result = runRetraceCommandLine(null, Arrays.asList(args));
     assertEquals(1, result.exitCode);
     assertThat(result.stderr, errorMatch);
   }
 
-  private ProcessResult runRetrace(String mapping, String stackTrace, boolean stacktraceStdIn)
+  private ProcessResult runRetrace(
+      String mapping, String stackTrace, boolean stacktraceStdIn, String... additionalArgs)
       throws IOException {
     Path mappingFile = folder.newFile("mapping.txt").toPath();
     Files.write(mappingFile, mapping.getBytes());
     File stackTraceFile = folder.newFile("stacktrace.txt");
     Files.write(stackTraceFile.toPath(), stackTrace.getBytes());
-    if (stacktraceStdIn) {
-      return runRetraceCommandLine(stackTraceFile, mappingFile.toString());
-    } else {
-      return runRetraceCommandLine(
-          null, mappingFile.toString(), stackTraceFile.toPath().toString());
+
+    Collection<String> args = new ArrayList<>();
+    args.add(mappingFile.toString());
+    if (!stacktraceStdIn) {
+      args.add(stackTraceFile.toPath().toString());
     }
+    args.addAll(Arrays.asList(additionalArgs));
+    return runRetraceCommandLine(stacktraceStdIn ? stackTraceFile : null, args);
   }
 
-  private ProcessResult runRetraceCommandLine(File stdInput, String... args) throws IOException {
+  private ProcessResult runRetraceCommandLine(File stdInput, Collection<String> args)
+      throws IOException {
     if (testExternal) {
       List<String> command = new ArrayList<>();
       command.add(ToolHelper.getSystemJavaExecutable());
@@ -117,7 +155,7 @@
       command.add("-cp");
       command.add(ToolHelper.R8_JAR.toString());
       command.add("com.android.tools.r8.retrace.Retrace");
-      command.addAll(Arrays.asList(args));
+      command.addAll(args);
       ProcessBuilder builder = new ProcessBuilder(command);
       if (stdInput != null) {
         builder.redirectInput(stdInput);
@@ -136,7 +174,9 @@
       System.setErr(new PrintStream(errorByteStream));
       int exitCode = 0;
       try {
-        Retrace.run(args);
+        String[] strArgs = new String[0];
+        strArgs = args.toArray(strArgs);
+        Retrace.run(strArgs);
       } catch (Throwable t) {
         exitCode = 1;
       }
@@ -149,7 +189,7 @@
           exitCode,
           outputByteStream.toString(),
           errorByteStream.toString(),
-          StringUtils.join(LINE_SEPARATOR, args));
+          StringUtils.joinLines(args));
     }
   }
 }
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 59a67ad..e29c928 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -43,7 +43,7 @@
   // 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 =
+  public static final String DEFAULT_REGULAR_EXPRESSION =
       "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)|(?:(?:(?:%c|.*)?[:\"]\\s+)?%c(?::.*)?)";
 
   @Parameters(name = "{0}, use regular expression: {1}")
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
new file mode 100644
index 0000000..277d190
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
@@ -0,0 +1,54 @@
+// 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 junit.framework.TestCase.assertEquals;
+
+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.stacktraces.FoundMethodVerboseStackTrace;
+import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
+import com.android.tools.r8.retrace.stacktraces.UnknownMethodVerboseStackTrace;
+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 RetraceVerboseTests extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RetraceVerboseTests(TestParameters parameters) {}
+
+  @Test
+  public void testFoundMethod() {
+    runRetraceTest(new FoundMethodVerboseStackTrace());
+  }
+
+  @Test
+  public void testUnknownMethod() {
+    runRetraceTest(new UnknownMethodVerboseStackTrace());
+  }
+
+  private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    RetraceCommand retraceCommand =
+        RetraceCommand.builder(diagnosticsHandler)
+            .setProguardMapProducer(stackTraceForTest::mapping)
+            .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
+            .setVerbose(true)
+            .setRetracedStackTraceConsumer(
+                retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
+            .build();
+    Retrace.run(retraceCommand);
+    return diagnosticsHandler;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
new file mode 100644
index 0000000..53aa7c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
@@ -0,0 +1,128 @@
+// 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.stacktraces;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ActualRetraceBotStackTraceWithInfo extends ActualBotStackTraceBase {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "com.android.tools.r8.CompilationFailedException: Compilation failed to complete",
+        "\tat com.android.tools.r8.BaseCommand$Builder.build(:6)",
+        "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:104)",
+        "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:29)",
+        "\tat com.android.tools.r8.TestCompilerBuilder.compile(TestCompilerBuilder.java:89)",
+        "\tat com.android.tools.r8.TestCompilerBuilder.run(TestCompilerBuilder.java:113)",
+        "\tat com.android.tools.r8.TestBuilder.run(TestBuilder.java:49)",
+        "\tat com.android.tools.r8.ir.optimize.classinliner.ClassInlinerTest.testCodeSample(ClassInlinerTest.java:289)",
+        "",
+        "Caused by:",
+        "com.android.tools.r8.utils.b: Error: offset: 158, line: 2, column: 33, Unexpected"
+            + " attribute at <no file>:2:33",
+        "-keepattributes -keepattributes LineNumberTable",
+        "                                ^",
+        "\tat com.android.tools.r8.utils.t0.a(:21)",
+        "\tat com.android.tools.r8.shaking.ProguardConfigurationParser.parse(:19)",
+        "\tat com.android.tools.r8.R8Command$Builder.i(:16)",
+        "\tat com.android.tools.r8.R8Command$Builder.b(:11)",
+        "\tat com.android.tools.r8.R8Command$Builder.b(:1)",
+        "\tat com.android.tools.r8.BaseCommand$Builder.build(:2)",
+        "\t... 6 more");
+  }
+
+  @Override
+  public String mapping() {
+    return r8MappingFromGitSha("dab96bbe5948133f0ae6e0a88fc133464421cf47");
+  }
+
+  // The Pruning lines is debug info printed by the command line interface if one passes --info and
+  // --regex=<DEFAULT_REGULAR_EXPRESSION> as options.
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Pruning \tat com.android.tools.r8.utils.Reporter.error(Reporter.java:21) from result"
+            + " because method is not defined on line number 21",
+        "Pruning \tat com.android.tools.r8.utils.Reporter.error(Reporter.java:21) from result"
+            + " because method is not defined on line number 21",
+        "Pruning \tat com.android.tools.r8.utils.Reporter.fatalError(Reporter.java:21) from result"
+            + " because method is not defined on line number 21",
+        "Pruning \tat"
+            + " com.android.tools.r8.utils.Reporter.addSuppressedExceptions(Reporter.java:21) from"
+            + " result because method is not defined on line number 21",
+        "Pruning \tat"
+            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
+            + " from result because method is not defined on line number 19",
+        "Pruning \tat"
+            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
+            + " from result because method is not defined on line number 19",
+        "Pruning \tat"
+            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
+            + " from result because method is not in range on line number 19",
+        "Pruning \tat"
+            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
+            + " from result because method is not in range on line number 19",
+        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:11) from"
+            + " result because method is not defined on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.setDisableVerticalClassMerging(R8Command.java:11)"
+            + " from result because method is not defined on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfigurationFiles$4(R8Command.java:11)"
+            + " from result because method is not defined on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:11)"
+            + " from result because method is not defined on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:11)"
+            + " from result because method is not defined on line number 11",
+        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:11) from"
+            + " result because method is not in range on line number 11",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.setDisableVerticalClassMerging(R8Command.java:1)"
+            + " from result because method is not defined on line number 1",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfigurationFiles$4(R8Command.java:1)"
+            + " from result because method is not defined on line number 1",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:1)"
+            + " from result because method is not defined on line number 1",
+        "Pruning \tat"
+            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:1)"
+            + " from result because method is not defined on line number 1",
+        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:1) from"
+            + " result because method is not defined on line number 1",
+        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:1) from"
+            + " result because method is not defined on line number 1",
+        "com.android.tools.r8.CompilationFailedException: Compilation failed to complete",
+        "\tat com.android.tools.r8.BaseCommand$Builder.build(BaseCommand.java:143)",
+        "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:104)",
+        "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:29)",
+        "\tat com.android.tools.r8.TestCompilerBuilder.compile(TestCompilerBuilder.java:89)",
+        "\tat com.android.tools.r8.TestCompilerBuilder.run(TestCompilerBuilder.java:113)",
+        "\tat com.android.tools.r8.TestBuilder.run(TestBuilder.java:49)",
+        "\tat com.android.tools.r8.ir.optimize.classinliner.ClassInlinerTest.testCodeSample(ClassInlinerTest.java:289)",
+        "",
+        "Caused by:",
+        "com.android.tools.r8.utils.AbortException: Error: offset: 158, line: 2, column: 33,"
+            + " Unexpected attribute at <no file>:2:33",
+        "-keepattributes -keepattributes LineNumberTable",
+        "                                ^",
+        "\tat com.android.tools.r8.utils.Reporter.failIfPendingErrors(Reporter.java:101)",
+        "\tat com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:187)",
+        "\tat com.android.tools.r8.R8Command$Builder.makeR8Command(R8Command.java:432)",
+        "\tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:413)",
+        "\tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:61)",
+        "\tat com.android.tools.r8.BaseCommand$Builder.build(BaseCommand.java:139)",
+        "\t... 6 more");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
new file mode 100644
index 0000000..4d9418c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
@@ -0,0 +1,38 @@
+// 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.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class FoundMethodVerboseStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException", "\tat a.a.a(Foo.java:7)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> a.a:",
+        "    7:7:com.android.Foo main(java.lang.String[],com.android.Bar):102 -> a");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.Foo com.android.tools.r8.naming.retrace.Main.main(java.lang.String[],"
+            + "com.android.Bar)(Main.java:102)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
new file mode 100644
index 0000000..3eb9710
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
@@ -0,0 +1,48 @@
+// 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.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class UnknownMethodVerboseStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat a.a.c(Foo.java)",
+        "\tat a.a.b(Bar.java)",
+        "\tat a.a.a(Baz.java)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> a.a:",
+        "    com.android.Foo main(java.lang.String[],com.android.Bar) -> a",
+        "    com.android.Foo main(java.lang.String[]) -> b",
+        "    com.android.Bar main(com.android.Bar) -> b");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.c(Main.java)",
+        "\tat com.android.Foo com.android.tools.r8.naming.retrace.Main.main("
+            + "java.lang.String[])(Main.java)",
+        "\t<OR> at com.android.Bar com.android.tools.r8.naming.retrace.Main.main("
+            + "com.android.Bar)(Main.java)",
+        "\tat com.android.Foo com.android.tools.r8.naming.retrace.Main.main("
+            + "java.lang.String[],com.android.Bar)(Main.java)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
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 ce9f1c5..2d79d48 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
@@ -10,9 +10,12 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.rewrite.assertions.testclasses.TestClassForInnerClass;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -21,7 +24,6 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
-import java.util.function.Function;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -65,15 +67,14 @@
   }
 
   private void runD8Test(
-      Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
-          assertionsConfigurationBuilder,
+      ThrowableConsumer<D8TestBuilder> builderConsumer,
       ThrowingConsumer<CodeInspector, RuntimeException> inspector,
       List<String> outputLines)
       throws Exception {
     testForD8()
         .addProgramClasses(testClasses)
         .setMinApi(parameters.getApiLevel())
-        .addAssertionsConfiguration(assertionsConfigurationBuilder)
+        .apply(builderConsumer)
         .compile()
         .inspect(inspector)
         .run(parameters.getRuntime(), TestClass.class)
@@ -81,17 +82,15 @@
   }
 
   public void runR8Test(
-      Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
-          assertionsConfigurationBuilder,
+      ThrowableConsumer<R8FullTestBuilder> builderConsumer,
       ThrowingConsumer<CodeInspector, RuntimeException> inspector,
       List<String> outputLines)
       throws Exception {
-    runR8Test(assertionsConfigurationBuilder, inspector, outputLines, false);
+    runR8Test(builderConsumer, inspector, outputLines, false);
   }
 
   public void runR8Test(
-      Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
-          assertionsConfigurationBuilder,
+      ThrowableConsumer<R8FullTestBuilder> builderConsumer,
       ThrowingConsumer<CodeInspector, RuntimeException> inspector,
       List<String> outputLines,
       boolean enableJvmAssertions)
@@ -102,7 +101,7 @@
         .addKeepMainRule(TestClass.class)
         .addKeepClassAndMembersRules(class1, class2, subpackageClass1, subpackageClass2)
         .setMinApi(parameters.getApiLevel())
-        .addAssertionsConfiguration(assertionsConfigurationBuilder)
+        .apply(builderConsumer)
         .compile()
         .inspect(inspector)
         .enableRuntimeAssertions(enableJvmAssertions)
@@ -110,18 +109,6 @@
         .assertSuccessWithOutput(StringUtils.lines(outputLines));
   }
 
-  private AssertionsConfiguration enableAllAssertions(AssertionsConfiguration.Builder builder) {
-    return builder.enable().build();
-  }
-
-  private AssertionsConfiguration disableAllAssertions(AssertionsConfiguration.Builder builder) {
-    return builder.disable().build();
-  }
-
-  private AssertionsConfiguration leaveAllAssertions(AssertionsConfiguration.Builder builder) {
-    return builder.passthrough().build();
-  }
-
   private List<String> allAssertionsExpectedLines() {
     return ImmutableList.of(
         "AssertionError in testclasses.Class1",
@@ -218,29 +205,53 @@
     Assume.assumeTrue(parameters.isDexRuntime());
     // Leaving assertions in or disabling them on Dalvik/Art means no assertions.
     runD8Test(
-        this::leaveAllAssertions, this::checkAssertionCodeLeft, noAllAssertionsExpectedLines());
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::passthroughAllAssertions),
+        this::checkAssertionCodeLeft,
+        noAllAssertionsExpectedLines());
     runR8Test(
-        this::leaveAllAssertions, this::checkAssertionCodeLeft, noAllAssertionsExpectedLines());
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::passthroughAllAssertions),
+        this::checkAssertionCodeLeft,
+        noAllAssertionsExpectedLines());
     runD8Test(
-        this::disableAllAssertions,
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::disableAllAssertions),
         this::checkAssertionCodeRemoved,
         noAllAssertionsExpectedLines());
     runR8Test(
-        this::disableAllAssertions,
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::disableAllAssertions),
         this::checkAssertionCodeRemoved,
         noAllAssertionsExpectedLines());
     // Compile time enabling assertions gives assertions on Dalvik/Art.
     runD8Test(
-        this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
-    runR8Test(
-        this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
-    // Enabling for the package should enable all.
-    runD8Test(
-        builder -> builder.enableForPackage(packageName).build(),
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::enableAllAssertions),
         this::checkAssertionCodeEnabled,
         allAssertionsExpectedLines());
     runR8Test(
-        builder -> builder.enableForPackage(packageName).build(),
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::enableAllAssertions),
+        this::checkAssertionCodeEnabled,
+        allAssertionsExpectedLines());
+    // Enabling for the package should enable all.
+    runD8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                b -> b.setEnable().setScopePackage(packageName).build()),
+        this::checkAssertionCodeEnabled,
+        allAssertionsExpectedLines());
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                b -> b.setEnable().setScopePackage(packageName).build()),
         this::checkAssertionCodeEnabled,
         allAssertionsExpectedLines());
   }
@@ -250,23 +261,42 @@
     Assume.assumeTrue(parameters.isCfRuntime());
     // Leaving assertion code means assertions are controlled by the -ea flag.
     runR8Test(
-        this::leaveAllAssertions, this::checkAssertionCodeLeft, noAllAssertionsExpectedLines());
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::passthroughAllAssertions),
+        this::checkAssertionCodeLeft,
+        noAllAssertionsExpectedLines());
     runR8Test(
-        this::leaveAllAssertions, this::checkAssertionCodeLeft, allAssertionsExpectedLines(), true);
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::passthroughAllAssertions),
+        this::checkAssertionCodeLeft,
+        allAssertionsExpectedLines(),
+        true);
     // Compile time enabling or disabling assertions means the -ea flag has no effect.
     runR8Test(
-        this::enableAllAssertions, this::checkAssertionCodeEnabled, allAssertionsExpectedLines());
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::enableAllAssertions),
+        this::checkAssertionCodeEnabled,
+        allAssertionsExpectedLines());
     runR8Test(
-        this::enableAllAssertions,
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::enableAllAssertions),
         this::checkAssertionCodeEnabled,
         allAssertionsExpectedLines(),
         true);
     runR8Test(
-        this::disableAllAssertions,
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::disableAllAssertions),
         this::checkAssertionCodeRemoved,
         noAllAssertionsExpectedLines());
     runR8Test(
-        this::disableAllAssertions,
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::disableAllAssertions),
         this::checkAssertionCodeRemoved,
         noAllAssertionsExpectedLines(),
         true);
@@ -276,7 +306,9 @@
   public void testEnableForPackageForDex() throws Exception {
     Assume.assumeTrue(parameters.isDexRuntime());
     runD8Test(
-        builder -> builder.enableForPackage(subPackageName).build(),
+        builder ->
+            builder.addAssertionsConfiguration(
+                b -> b.setEnable().setScopePackage(subPackageName).build()),
         inspector -> {
           checkAssertionCodeEnabled(inspector, subpackageClass1);
           checkAssertionCodeEnabled(inspector, subpackageClass2);
@@ -286,7 +318,9 @@
             "AssertionError in testclasses.subpackage.Class2",
             "DONE"));
     runR8Test(
-        builder -> builder.enableForPackage(subPackageName).build(),
+        builder ->
+            builder.addAssertionsConfiguration(
+                b -> b.setEnable().setScopePackage(subPackageName).build()),
         inspector -> {
           checkAssertionCodeEnabled(inspector, subpackageClass1);
           checkAssertionCodeEnabled(inspector, subpackageClass2);
@@ -303,12 +337,13 @@
     runD8Test(
         builder ->
             builder
-                .enableForClass(class1.getCanonicalName())
-                .enableForClass(subpackageClass2.getCanonicalName())
-                .build(),
+                .addAssertionsConfiguration(
+                    b -> b.setEnable().setScopeClass(class1.getCanonicalName()).build())
+                .addAssertionsConfiguration(
+                    b -> b.setEnable().setScopeClass(subpackageClass2.getCanonicalName()).build()),
         inspector -> {
-          // checkAssertionCodeEnabled(inspector, class1);
-          // checkAssertionCodeEnabled(inspector, subpackageClass2);
+          checkAssertionCodeEnabled(inspector, class1);
+          checkAssertionCodeEnabled(inspector, subpackageClass2);
         },
         ImmutableList.of(
             "AssertionError in testclasses.Class1",
@@ -317,9 +352,10 @@
     runR8Test(
         builder ->
             builder
-                .enableForClass(class1.getCanonicalName())
-                .enableForClass(subpackageClass2.getCanonicalName())
-                .build(),
+                .addAssertionsConfiguration(
+                    b -> b.setEnable().setScopeClass(class1.getCanonicalName()).build())
+                .addAssertionsConfiguration(
+                    b -> b.setEnable().setScopeClass(subpackageClass2.getCanonicalName()).build()),
         inspector -> {
           checkAssertionCodeEnabled(inspector, class1);
           checkAssertionCodeEnabled(inspector, subpackageClass2);
@@ -336,10 +372,11 @@
     runD8Test(
         builder ->
             builder
-                .enableForPackage(packageName)
-                .disableForClass(class2.getCanonicalName())
-                .disableForClass(subpackageClass1.getCanonicalName())
-                .build(),
+                .addAssertionsConfiguration(b -> b.setEnable().setScopePackage(packageName).build())
+                .addAssertionsConfiguration(
+                    b -> b.setDisable().setScopeClass(class2.getCanonicalName()).build())
+                .addAssertionsConfiguration(
+                    b -> b.setDisable().setScopeClass(subpackageClass1.getCanonicalName()).build()),
         inspector -> {
           checkAssertionCodeEnabled(inspector, class1);
           checkAssertionCodeRemoved(inspector, class2);
@@ -353,10 +390,11 @@
     runR8Test(
         builder ->
             builder
-                .enableForPackage(packageName)
-                .disableForClass(class2.getCanonicalName())
-                .disableForClass(subpackageClass1.getCanonicalName())
-                .build(),
+                .addAssertionsConfiguration(b -> b.setEnable().setScopePackage(packageName).build())
+                .addAssertionsConfiguration(
+                    b -> b.setDisable().setScopeClass(class2.getCanonicalName()).build())
+                .addAssertionsConfiguration(
+                    b -> b.setDisable().setScopeClass(subpackageClass1.getCanonicalName()).build()),
         inspector -> {
           checkAssertionCodeEnabled(inspector, class1);
           checkAssertionCodeRemoved(inspector, class2);
@@ -379,7 +417,7 @@
             classInUnnamedPackage("Class1"),
             classInUnnamedPackage("Class2"))
         .setMinApi(parameters.getApiLevel())
-        .addAssertionsConfiguration(builder -> builder.enableForPackage("").build())
+        .addAssertionsConfiguration(builder -> builder.setEnable().setScopePackage("").build())
         .compile()
         .inspect(
             inspector -> {
@@ -427,7 +465,10 @@
         .setMinApi(parameters.getApiLevel())
         .addAssertionsConfiguration(
             builder ->
-                builder.enableForClass(TestClassForInnerClass.class.getCanonicalName()).build())
+                builder
+                    .setEnable()
+                    .setScopeClass(TestClassForInnerClass.class.getCanonicalName())
+                    .build())
         .compile()
         .inspect(
             inspector -> {
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 27e53ba..bbcc8e2 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,8 @@
         .debug()
         .noTreeShaking()
         .noMinification()
-        .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
+        .addAssertionsConfiguration(
+            builder -> builder.setTransformation(transformation).setScopeAll().build())
         .compile();
   }
 
@@ -337,7 +338,8 @@
         .addProgramClasses(ClassWithAssertions.class)
         .debug()
         .setMinApi(AndroidApiLevel.B)
-        .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
+        .addAssertionsConfiguration(
+            builder -> builder.setTransformation(transformation).setScopeAll().build())
         .compile();
   }
 
@@ -357,7 +359,8 @@
         .addProgramFiles(program)
         .debug()
         .setMinApi(AndroidApiLevel.B)
-        .addAssertionsConfiguration(builder -> builder.setTransformation(transformation).build())
+        .addAssertionsConfiguration(
+            builder -> builder.setTransformation(transformation).setScopeAll().build())
         .compile();
   }
 
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 3d653d0..598f6f1 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -166,6 +166,31 @@
         });
   }
 
+  /** Unconditionally replace the descriptor (ie, qualified name) of a class. */
+  public ClassFileTransformer setClassDescriptor(String descriptor) {
+    assert DescriptorUtils.isClassDescriptor(descriptor);
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String ignoredName,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            super.visit(
+                version,
+                access,
+                DescriptorUtils.getBinaryNameFromDescriptor(descriptor),
+                signature,
+                superName,
+                interfaces);
+          }
+        });
+  }
+
+
   public ClassFileTransformer setMinVersion(CfVm jdk) {
     return setMinVersion(jdk.getClassfileVersion());
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 071fb4f..1051e28 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -134,7 +134,7 @@
 
   @Override
   public boolean isVirtual() {
-    return dexMethod.isVirtualMethod();
+    return dexMethod.isNonPrivateVirtualMethod();
   }
 
   @Override
diff --git a/tests/d8_api_usage_sample.jar b/tests/d8_api_usage_sample.jar
index 30b00cb..e47a3ad 100644
--- a/tests/d8_api_usage_sample.jar
+++ b/tests/d8_api_usage_sample.jar
Binary files differ
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
index 4c4801c..4970fe1 100644
--- a/tests/r8_api_usage_sample.jar
+++ b/tests/r8_api_usage_sample.jar
Binary files differ
diff --git a/tools/retrace.py b/tools/retrace.py
index 6f3593f..e8fdf89 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -71,8 +71,13 @@
       return 1
 
   retrace_args = [
-    jdk.GetJavaExecutable(), '-jar', utils.RETRACE_JAR, r8lib_map_path
+    jdk.GetJavaExecutable(),
+    '-cp',
+    utils.R8LIB_JAR,
+    'com.android.tools.r8.retrace.Retrace',
+    r8lib_map_path
   ]
+
   if args.stacktrace:
     retrace_args.append(args.stacktrace)
 
diff --git a/tools/test_self_retrace.py b/tools/test_self_retrace.py
deleted file mode 100755
index d7ab9aa..0000000
--- a/tools/test_self_retrace.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2018, 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.
-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import gradle
-import jdk
-import os
-import subprocess
-import sys
-
-import utils
-
-EXCEPTION_LINE = 'Intentional exception for testing retrace.'
-EXPECTED_LINES = [
-  'com.android.tools.r8.utils.SelfRetraceTest.foo3(SelfRetraceTest.java:13)',
-  'com.android.tools.r8.utils.SelfRetraceTest.foo2(SelfRetraceTest.java:17)',
-  'com.android.tools.r8.utils.SelfRetraceTest.foo1(SelfRetraceTest.java:21)',
-  'com.android.tools.r8.utils.SelfRetraceTest.test(SelfRetraceTest.java:26)',
-  'com.android.tools.r8.R8.run(R8.java:',
-]
-
-def main():
-  args = sys.argv[1:]
-  if len(args) == 0:
-    gradle.RunGradle(['r8lib'])
-    r8lib = utils.R8LIB_JAR
-    r8map = utils.R8LIB + '.map'
-  elif len(args) == 2:
-    r8lib = args[0]
-    r8map = args[1]
-  elif len(args) == 1 and args[0] == '--help':
-    print('Usage: test_self_retrace.py [<path-to-r8lib-jar> <path-to-r8lib-map]')
-    print('If the path is missing the script builds and uses ' + utils.R8LIB_JAR)
-    return
-  else:
-    raise Exception("Only two argument allowed, see '--help'.")
-
-  # Run 'r8 --help' which throws an exception.
-  cmd = [
-    jdk.GetJavaExecutable(),'-cp', r8lib, 'com.android.tools.r8.R8', '--help'
-  ]
-  os.environ["R8_THROW_EXCEPTION_FOR_TESTING_RETRACE"] = "1"
-  utils.PrintCmd(cmd)
-  p = subprocess.Popen(cmd, stderr=subprocess.PIPE)
-  _, stacktrace = p.communicate()
-  assert(p.returncode != 0)
-  assert(EXCEPTION_LINE in stacktrace)
-  # r8lib must be minified, original class names must not be present.
-  assert('SelfRetraceTest' not in stacktrace)
-
-  # Run the retrace tool.
-  cmd = [jdk.GetJavaExecutable(), '-jar', utils.RETRACE_JAR, r8map]
-  utils.PrintCmd(cmd)
-  p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
-  retrace_stdout, _ = p.communicate(stacktrace)
-  assert p.returncode == 0
-  retrace_lines = retrace_stdout.splitlines()
-  line_index = -1
-  for line in retrace_lines:
-    if line_index < 0:
-      if 'java.lang.RuntimeException' in line:
-        assert(EXCEPTION_LINE in line)
-        line_index = 0;
-    else:
-      assert EXPECTED_LINES[line_index] in line
-      line_index += 1
-      if line_index >= len(EXPECTED_LINES):
-         break
-  assert(line_index >= 0)
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/tools/utils.py b/tools/utils.py
index 51c5a12..6690ba2 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -60,12 +60,6 @@
 GENERATED_LICENSE = os.path.join(GENERATED_LICENSE_DIR, 'LICENSE')
 RT_JAR = os.path.join(REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
 R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
-RETRACE_JAR = os.path.join(
-    THIRD_PARTY,
-    'proguard',
-    'proguard6.0.1',
-    'lib',
-    'retrace.jar')
 PROGUARD_JAR = os.path.join(
     THIRD_PARTY,
     'proguard',