Merge commit '4a6cc7f317bd1b1968461b2688f51f6debbea91d' into dev-release
diff --git a/build.gradle b/build.gradle
index fba205d..9800595 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1822,6 +1822,7 @@
         systemProperty 'test_git_HEAD_sha1', project.property('HEAD_sha1')
     }
 
+    dependsOn buildLibraryDesugarConversions
     dependsOn getJarsFromSupportLibs
     // R8.jar is required for running bootstrap tests.
     dependsOn R8
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 3cfd647..7d2924f 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -1,6 +1,6 @@
 {
   "configuration_format_version": 3,
-  "version": "0.9.0",
+  "version": "0.11.0",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "library_flags": [
@@ -16,6 +16,10 @@
         "java.lang.Integer8": "java.lang.Integer",
         "java.lang.Long8": "java.lang.Long",
         "java.lang.Math8": "java.lang.Math"
+      },
+      "retarget_lib_member": {
+        "java.util.Date#toInstant": "java.util.DesugarDate",
+        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar"
       }
     },
     {
@@ -52,6 +56,7 @@
         "java.util.Iterator#remove"
       ],
       "emulate_interface": {
+        "java.lang.Iterable": "j$.lang.Iterable",
         "java.util.Map$Entry": "j$.util.Map$Entry",
         "java.util.Collection": "j$.util.Collection",
         "java.util.Map": "j$.util.Map",
@@ -66,7 +71,10 @@
         "java.util.Optional": "j$.util.OptionalConversions",
         "java.util.OptionalDouble": "j$.util.OptionalConversions",
         "java.util.OptionalInt": "j$.util.OptionalConversions",
-        "java.util.OptionalLong": "j$.util.OptionalConversions"
+        "java.util.OptionalLong": "j$.util.OptionalConversions",
+        "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatisticsConversions",
+        "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatisticsConversions",
+        "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatisticsConversions"
       }
     }
   ],
@@ -124,12 +132,15 @@
         "java.util.concurrent.atomic.AtomicReference#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicReference",
         "java.util.concurrent.atomic.AtomicReference#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference",
         "java.util.concurrent.atomic.AtomicReference#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicReference",
-        "java.util.concurrent.atomic.AtomicReference#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference"
+        "java.util.concurrent.atomic.AtomicReference#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference",
+        "java.util.Collections#synchronizedMap": "java.util.DesugarCollections",
+        "java.util.Collections#synchronizedSortedMap": "java.util.DesugarCollections"
       },
       "dont_rewrite": [
         "java.util.Iterator#remove"
       ],
       "emulate_interface": {
+        "java.lang.Iterable": "j$.lang.Iterable",
         "java.util.Map$Entry": "j$.util.Map$Entry",
         "java.util.Collection": "j$.util.Collection",
         "java.util.Map": "j$.util.Map",
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 054db58..2915114 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -4,14 +4,19 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
 public class BaseCompilerCommandParser {
 
   static void parseMinApi(BaseCompilerCommand.Builder builder, String minApiString, Origin origin) {
     int minApi;
     try {
-      minApi = Integer.valueOf(minApiString);
+      minApi = Integer.parseInt(minApiString);
     } catch (NumberFormatException e) {
       builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString, origin));
       return;
@@ -22,4 +27,40 @@
     }
     builder.setMinApiLevel(minApi);
   }
+
+  /**
+   * This method must match the lookup in
+   * {@link com.android.tools.r8.JdkClassFileProvider#fromJdkHome}.
+   */
+  private static boolean isJdkHome(Path home) {
+    Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+    if (Files.exists(jrtFsJar)) {
+      return true;
+    }
+    // JDK has rt.jar in jre/lib/rt.jar.
+    Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar");
+    if (Files.exists(rtJar)) {
+      return true;
+    }
+    // JRE has rt.jar in lib/rt.jar.
+    rtJar = home.resolve("lib").resolve("rt.jar");
+    if (Files.exists(rtJar)) {
+      return true;
+    }
+    return false;
+  }
+
+  static void addLibraryArgument(BaseCommand.Builder builder, Origin origin, String arg) {
+    Path path = Paths.get(arg);
+    if (isJdkHome(path)) {
+      try {
+        builder
+            .addLibraryResourceProvider(JdkClassFileProvider.fromJdkHome(path));
+      } catch (IOException e) {
+        builder.error(new ExceptionDiagnostic(e, origin));
+      }
+    } else {
+      builder.addLibraryFiles(path);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 3985cd7..792f8b9 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -115,7 +115,7 @@
               "  --release               # Compile without debugging information.",
               "  --output <file>         # Output result in <outfile>.",
               "                          # <file> must be an existing directory or a zip file.",
-              "  --lib <file>            # Add <file> as a library resource.",
+              "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
               "  --classpath <file>      # Add <file> as a classpath resource.",
               "  --min-api <number>      # Minimum Android API level compatibility, default: "
                   + AndroidApiLevel.getDefault().getLevel()
@@ -209,7 +209,7 @@
         }
         outputPath = Paths.get(nextArg);
       } else if (arg.equals("--lib")) {
-        builder.addLibraryFiles(Paths.get(nextArg));
+        addLibraryArgument(builder, origin, nextArg);
       } else if (arg.equals("--classpath")) {
         Path file = Paths.get(nextArg);
         try {
diff --git a/src/main/java/com/android/tools/r8/JdkClassFileProvider.java b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
index 5e35cf0..9fcbd00 100644
--- a/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
@@ -30,7 +30,9 @@
  * <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for
  * resources in the descriptor set will then force the read of JDK content.
  *
- * <p>Currently only JDK's of version 9 or higher with a lib/jrt-fs.jar file present is supported.
+ * <p>This supports all JDK versions. For JDK's of version 8 or lower classes in
+ * <code>lib/rt.jar</code> will be loaded. JDK's of version 9 or higher system module classes will
+ * be loaded using <code>lib/jrt-fs.jar/<code>.
  */
 @Keep
 public class JdkClassFileProvider implements ClassFileResourceProvider, Closeable {
@@ -65,11 +67,11 @@
   }
 
   /**
-   * Creates a lazy class-file program-resource provider for the runtime of a JDK.
+   * Creates a lazy class-file program-resource provider for a JDK.
    *
-   * <p>This will load the program-resources form the system modules for JDK of version 9 or higher.
+   * <p>This will load the program resources form the system modules for JDK of version 9 or higher.
    *
-   * <p>This will load <code>rt.jar</code> for JDK of version 8 and lower.
+   * <p>This will load <code>lib/rt.jar</code> for JDK of version 8 and lower.
    *
    * @param home Location of the JDK to read the program-resources from.
    */
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 1ce3316..b4ae1c8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
@@ -29,6 +30,7 @@
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.SourceDebugExtensionRewriter;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
@@ -318,6 +320,13 @@
         assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
 
         appView.rootSet().checkAllRulesAreUsed(options);
+
+        if (appView.options().enableSourceDebugExtensionRewriter) {
+          appView.setSourceDebugExtensionRewriter(
+              new SourceDebugExtensionRewriter(appView)
+                  .analyze(appView.withLiveness().appInfo()::isLiveProgramClass));
+        }
+
         if (options.proguardSeedsConsumer != null) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
@@ -363,7 +372,7 @@
       }
 
       assert appView.appInfo().hasLiveness();
-
+      assert verifyNoJarApplicationReaders(application.classes());
       // Build conservative main dex content after first round of tree shaking. This is used
       // by certain optimizations to avoid introducing additional class references into main dex
       // classes, as that can cause the final number of main dex methods to grow.
@@ -883,6 +892,17 @@
     }
   }
 
+  private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
+    for (DexProgramClass clazz : classes) {
+      for (DexEncodedMethod method : clazz.methods()) {
+        if (method.getCode() != null) {
+          assert method.getCode().verifyNoInputReaders();
+        }
+      }
+    }
+    return true;
+  }
+
   private static void run(String[] args) throws CompilationFailedException {
     R8Command command = R8Command.parse(args, CommandLineOrigin.INSTANCE).build();
     if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index fd4f570..b0acdb6 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -58,7 +58,7 @@
               "  --classfile              # Compile program to Java classfile format.",
               "  --output <file>          # Output result in <file>.",
               "                           # <file> must be an existing directory or a zip file.",
-              "  --lib <file>             # Add <file> as a library resource.",
+              "  --lib <file|jdk-home>    # Add <file|jdk-home> as a library resource.",
               "  --classpath <file>       # Add <file> as a classpath resource.",
               "  --min-api <number>       # Minimum Android API level compatibility, default: "
                   + AndroidApiLevel.getDefault().getLevel()
@@ -178,7 +178,7 @@
         }
         state.outputPath = Paths.get(nextArg);
       } else if (arg.equals("--lib")) {
-        builder.addLibraryFiles(Paths.get(nextArg));
+        addLibraryArgument(builder, argsOrigin, nextArg);
       } else if (arg.equals("--classpath")) {
         builder.addClasspathFiles(Paths.get(nextArg));
       } else if (arg.equals("--min-api")) {
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 0248418..e30a78d 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -493,7 +493,7 @@
     newline();
     instructionIndex();
     builder.append(getLabel(label)).append(':');
-    if (PRINT_INLINE_LOCALS) {
+    if (PRINT_INLINE_LOCALS && labelToIndex != null) {
       int labelNumber = labelToIndex.getInt(label);
       List<LocalVariableInfo> locals = localsAtLabel.get(labelNumber);
       appendComment(
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 66f7051..2801ca8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -77,6 +77,14 @@
     return false;
   }
 
+  public CfPosition asPosition() {
+    return null;
+  }
+
+  public boolean isPosition() {
+    return false;
+  }
+
   public CfLoad asLoad() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 684ee26..9302711 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -50,6 +50,16 @@
   }
 
   @Override
+  public boolean isPosition() {
+    return true;
+  }
+
+  @Override
+  public CfPosition asPosition() {
+    return this;
+  }
+
+  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     Position canonical = code.getCanonicalPosition(position);
     state.setPosition(canonical);
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 1f95782..d549e28 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -179,6 +179,10 @@
     return false;
   }
 
+  public boolean isThrow() {
+    return false;
+  }
+
   public int getPayloadOffset() {
     return 0;
   }
diff --git a/src/main/java/com/android/tools/r8/code/Throw.java b/src/main/java/com/android/tools/r8/code/Throw.java
index 247b475..40d69f1 100644
--- a/src/main/java/com/android/tools/r8/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/code/Throw.java
@@ -43,4 +43,9 @@
   public boolean canThrow() {
     return true;
   }
+
+  @Override
+  public boolean isThrow() {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
new file mode 100644
index 0000000..3b4d52f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+/**
+ * Definitions of access control routines.
+ *
+ * <p>Follows SE 11, jvm spec, section 5.4.4 on "Access Control", except for aspects related to
+ * "run-time module", for which all items are assumed to be in the same single such module.
+ */
+public class AccessControl {
+
+  public static boolean isClassAccessible(DexClass clazz, DexProgramClass context) {
+    if (clazz.accessFlags.isPublic()) {
+      return true;
+    }
+    return clazz.getType().isSamePackage(context.getType());
+  }
+
+  public static boolean isMethodAccessible(
+      DexEncodedMethod method,
+      DexClass holder,
+      DexProgramClass context,
+      AppInfoWithSubtyping appInfo) {
+    return isMemberAccessible(method.accessFlags, holder, context, appInfo);
+  }
+
+  public static boolean isFieldAccessible(
+      DexEncodedField field,
+      DexClass holder,
+      DexProgramClass context,
+      AppInfoWithSubtyping appInfo) {
+    return isMemberAccessible(field.accessFlags, holder, context, appInfo);
+  }
+
+  private static boolean isMemberAccessible(
+      AccessFlags<?> memberFlags,
+      DexClass holder,
+      DexProgramClass context,
+      AppInfoWithSubtyping appInfo) {
+    if (!isClassAccessible(holder, context)) {
+      return false;
+    }
+    if (memberFlags.isPublic()) {
+      return true;
+    }
+    if (memberFlags.isPrivate()) {
+      return isNestMate(holder, context);
+    }
+    if (holder.getType().isSamePackage(context.getType())) {
+      return true;
+    }
+    if (!memberFlags.isProtected()) {
+      return false;
+    }
+    return appInfo.isSubtype(context.getType(), holder.getType());
+  }
+
+  private static boolean isNestMate(DexClass clazz, DexProgramClass context) {
+    if (clazz == context) {
+      return true;
+    }
+    if (!clazz.isInANest() || !context.isInANest()) {
+      return false;
+    }
+    return clazz.getNestHost() == context.getNestHost();
+  }
+}
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 3b51579..2390bc6 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import java.util.ArrayList;
@@ -19,6 +20,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class AppInfo implements DexDefinitionSupplier {
@@ -88,6 +90,7 @@
     assert checkIfObsolete();
     assert clazz.type.isD8R8SynthesizedClassType();
     DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
+    invalidateTypeCacheFor(clazz.type);
     assert previous == null || previous == clazz;
   }
 
@@ -98,7 +101,7 @@
 
   private Map<Descriptor<?,?>, KeyedDexItem<?>> computeDefinitions(DexType type) {
     Builder<Descriptor<?,?>, KeyedDexItem<?>> builder = ImmutableMap.builder();
-    DexClass clazz = app.definitionFor(type);
+    DexClass clazz = definitionFor(type);
     if (clazz != null) {
       clazz.forEachMethod(method -> builder.put(method.getKey(), method));
       clazz.forEachField(field -> builder.put(field.getKey(), field));
@@ -212,24 +215,7 @@
    */
   public DexEncodedMethod lookupSuperTarget(DexMethod method, DexType invocationContext) {
     assert checkIfObsolete();
-    // Make sure we are not chasing NotFoundError.
-    if (resolveMethod(method.holder, method).getSingleTarget() == null) {
-      return null;
-    }
-    // According to
-    // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial, use
-    // the "symbolic reference" if the "symbolic reference" does not name a class.
-    if (definitionFor(method.holder).isInterface()) {
-      return resolveMethodOnInterface(method.holder, method).getSingleTarget();
-    }
-    // Then, resume on the search, but this time, starting from the holder of the caller.
-    DexClass contextClass = definitionFor(invocationContext);
-    if (contextClass == null || contextClass.superType == null) {
-      return null;
-    }
-    ResolutionResult resolutionResult = resolveMethod(contextClass.superType, method);
-    DexEncodedMethod target = resolutionResult.getSingleTarget();
-    return target == null || !target.isStatic() ? target : null;
+    return resolveMethod(method.holder, method).lookupInvokeSuperTarget(invocationContext, this);
   }
 
   /**
@@ -355,50 +341,67 @@
     assert checkIfObsolete();
     assert !clazz.isInterface();
     // Step 2:
-    DexEncodedMethod singleTarget = resolveMethodOnClassStep2(clazz, method);
-    if (singleTarget != null) {
-      return new SingleResolutionResult(singleTarget);
+    SingleResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
+    if (result != null) {
+      return result;
     }
     // Finally Step 3:
     return resolveMethodStep3(clazz, method);
   }
 
   /**
-   * Implements step 2 of method resolution on classes as per
-   * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
-   * Section 5.4.3.3 of the JVM Spec</a>.
+   * Implements step 2 of method resolution on classes as per <a
+   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
+   * 5.4.3.3 of the JVM Spec</a>.
    */
-  private DexEncodedMethod resolveMethodOnClassStep2(DexClass clazz, DexMethod method) {
+  private SingleResolutionResult resolveMethodOnClassStep2(
+      DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
     // Pt. 1: Signature polymorphic method check.
     // See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
     // Section 2.9 of the JVM Spec</a>.
     DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(method.name, dexItemFactory);
     if (result != null) {
-      return result;
+      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
     }
     // Pt 2: Find a method that matches the descriptor.
     result = clazz.lookupMethod(method);
     if (result != null) {
-      return result;
+      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
     }
     // Pt 3: Apply step two to direct superclass of holder.
     if (clazz.superType != null) {
       DexClass superClass = definitionFor(clazz.superType);
       if (superClass != null) {
-        return resolveMethodOnClassStep2(superClass, method);
+        return resolveMethodOnClassStep2(superClass, method, initialResolutionHolder);
       }
     }
     return null;
   }
 
   /**
-   * Implements step 3 of
-   * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
-   * Section 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share
-   * one implementation.
+   * Helper method used for emulated interface resolution (not in JVM specifications). The result
+   * may be abstract.
+   */
+  public ResolutionResult resolveMaximallySpecificMethods(DexClass clazz, DexMethod method) {
+    assert !clazz.type.isArrayType();
+    if (clazz.isInterface()) {
+      // Look for exact method on interface.
+      DexEncodedMethod result = clazz.lookupMethod(method);
+      if (result != null) {
+        return new SingleResolutionResult(clazz, clazz, result);
+      }
+    }
+    return resolveMethodStep3(clazz, method);
+  }
+
+  /**
+   * Implements step 3 of <a
+   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
+   * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
+   * implementation.
    */
   private ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
-    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder(clazz);
     resolveMethodStep3Helper(clazz, method, builder);
     return builder.resolve();
   }
@@ -475,7 +478,7 @@
     // Step 2: Look for exact method on interface.
     DexEncodedMethod result = definition.lookupMethod(desc);
     if (result != null) {
-      return new SingleResolutionResult(result);
+      return new SingleResolutionResult(definition, definition, result);
     }
     // Step 3: Look for matching method on object class.
     DexClass objectClass = definitionFor(dexItemFactory.objectType);
@@ -484,7 +487,7 @@
     }
     result = objectClass.lookupMethod(desc);
     if (result != null && result.accessFlags.isPublic() && !result.accessFlags.isAbstract()) {
-      return new SingleResolutionResult(result);
+      return new SingleResolutionResult(definition, objectClass, result);
     }
     // Step 3: Look for maximally-specific superinterface methods or any interface definition.
     //         This is the same for classes and interfaces.
@@ -590,6 +593,8 @@
 
   private static class MaximallySpecificMethodsBuilder {
 
+    private final DexClass initialResolutionHolder;
+
     // The set of actual maximally specific methods.
     // This set is linked map so that in the case where a number of methods remain a deterministic
     // choice can be made. The map is from definition classes to their maximally specific method, or
@@ -598,6 +603,10 @@
     // prior to writing.
     LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods = new LinkedHashMap<>();
 
+    public MaximallySpecificMethodsBuilder(DexClass initialResolutionHolder) {
+      this.initialResolutionHolder = initialResolutionHolder;
+    }
+
     void addCandidate(DexClass holder, DexEncodedMethod method, AppInfo appInfo) {
       // If this candidate is already a candidate or it is shadowed, then no need to continue.
       if (maximallySpecificMethods.containsKey(holder)) {
@@ -640,32 +649,42 @@
       }
       // Fast path in the common case of a single method.
       if (maximallySpecificMethods.size() == 1) {
-        return new SingleResolutionResult(maximallySpecificMethods.values().iterator().next());
+        Entry<DexClass, DexEncodedMethod> first =
+            maximallySpecificMethods.entrySet().iterator().next();
+        return new SingleResolutionResult(
+            initialResolutionHolder, first.getKey(), first.getValue());
       }
-      DexEncodedMethod firstMaximallySpecificMethod = null;
-      List<DexEncodedMethod> nonAbstractMethods = new ArrayList<>(maximallySpecificMethods.size());
-      for (DexEncodedMethod method : maximallySpecificMethods.values()) {
+      Entry<DexClass, DexEncodedMethod> firstMaximallySpecificMethod = null;
+      List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods =
+          new ArrayList<>(maximallySpecificMethods.size());
+      for (Entry<DexClass, DexEncodedMethod> entry : maximallySpecificMethods.entrySet()) {
+        DexEncodedMethod method = entry.getValue();
         if (method == null) {
           // Ignore shadowed candidates.
           continue;
         }
         if (firstMaximallySpecificMethod == null) {
-          firstMaximallySpecificMethod = method;
+          firstMaximallySpecificMethod = entry;
         }
         if (method.isNonAbstractVirtualMethod()) {
-          nonAbstractMethods.add(method);
+          nonAbstractMethods.add(entry);
         }
       }
       // If there are no non-abstract methods, then any candidate will suffice as a target.
       // For deterministic resolution, we return the first mapped method (of the linked map).
       if (nonAbstractMethods.isEmpty()) {
-        return new SingleResolutionResult(firstMaximallySpecificMethod);
+        return new SingleResolutionResult(
+            initialResolutionHolder,
+            firstMaximallySpecificMethod.getKey(),
+            firstMaximallySpecificMethod.getValue());
       }
       // If there is exactly one non-abstract method (a default method) it is the resolution target.
       if (nonAbstractMethods.size() == 1) {
-        return new SingleResolutionResult(nonAbstractMethods.get(0));
+        Entry<DexClass, DexEncodedMethod> entry = nonAbstractMethods.get(0);
+        return new SingleResolutionResult(
+            initialResolutionHolder, entry.getKey(), entry.getValue());
       }
-      return IncompatibleClassResult.create(nonAbstractMethods);
+      return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index e7ca421..0998136 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -5,10 +5,8 @@
 
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
@@ -330,39 +328,6 @@
     return true;
   }
 
-  /**
-   * Lookup super method following the super chain from the holder of {@code method}.
-   *
-   * <p>This method will resolve the method on the holder of {@code method} and only return a
-   * non-null value if the result of resolution was an instance (i.e. non-static) method.
-   *
-   * <p>Additionally, this will also verify that the invoke super is valid, i.e., it is on the same
-   * type or a super type of the current context. The spec says that it has invoke super semantics,
-   * if the type is a supertype of the current class. If it is the same or a subtype, it has invoke
-   * direct semantics. The latter case is illegal, so we map it to a super call here. In R8, we
-   * abort at a later stage (see. See also <a href=
-   * "https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial" </a>
-   * for invokespecial dispatch and <a href="https://docs.oracle.com/javase/specs/jvms/"
-   * "se7/html/jvms-4.html#jvms-4.10.1.9.invokespecial"</a> for verification requirements. In
-   * particular, the requirement isAssignable(class(CurrentClassName, L), class(MethodClassName,
-   * L)). com.android.tools.r8.cf.code.CfInvoke#isInvokeSuper(DexType)}.
-   *
-   * @param method the method to lookup
-   * @param invocationContext the class the invoke is contained in, i.e., the holder of the caller.
-   * @return The actual target for {@code method} or {@code null} if none found.
-   */
-  @Override
-  public DexEncodedMethod lookupSuperTarget(DexMethod method, DexType invocationContext) {
-    assert checkIfObsolete();
-    if (!isSubtype(invocationContext, method.holder)) {
-      DexClass contextClass = definitionFor(invocationContext);
-      throw new CompilationError(
-          "Illegal invoke-super to " + method.toSourceString() + " from class " + invocationContext,
-          contextClass != null ? contextClass.getOrigin() : Origin.unknown());
-    }
-    return super.lookupSuperTarget(method, invocationContext);
-  }
-
   protected boolean hasAnyInstantiatedLambdas(DexProgramClass clazz) {
     assert checkIfObsolete();
     return true; // Don't know, there might be.
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 99a2a2b..91994d1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.conversion.SourceDebugExtensionRewriter;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -57,6 +58,8 @@
   private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
   private VerticallyMergedClasses verticallyMergedClasses;
 
+  private SourceDebugExtensionRewriter sourceDebugExtensionRewriter;
+
   private AppView(
       T appInfo, WholeProgramOptimizations wholeProgramOptimizations, InternalOptions options) {
     this(
@@ -141,6 +144,14 @@
     allCodeProcessed = true;
   }
 
+  public void setSourceDebugExtensionRewriter(SourceDebugExtensionRewriter rewriter) {
+    this.sourceDebugExtensionRewriter = rewriter;
+  }
+
+  public SourceDebugExtensionRewriter getSourceDebugExtensionRewriter() {
+    return this.sourceDebugExtensionRewriter;
+  }
+
   public AppServices appServices() {
     return appServices;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 003b3df..b9e8757 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -92,7 +92,7 @@
 
   private final int maxStack;
   private final int maxLocals;
-  public final List<CfInstruction> instructions;
+  public List<CfInstruction> instructions;
   private final List<CfTryCatch> tryCatchRanges;
   private final List<LocalVariableInfo> localVariables;
 
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 242f953..0fbae38 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -95,4 +95,8 @@
   }
 
   public abstract boolean isEmptyVoidMethod();
+
+  public boolean verifyNoInputReaders() {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 78ba461..358af04 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -968,6 +968,21 @@
     return getKotlinInfo() != null;
   }
 
+  public boolean hasInstanceFields() {
+    return instanceFields.length > 0;
+  }
+
+  public boolean hasInstanceFieldsDirectlyOrIndirectly(AppView<?> appView) {
+    if (superType == null || type == appView.dexItemFactory().objectType) {
+      return false;
+    }
+    if (hasInstanceFields()) {
+      return true;
+    }
+    DexClass superClass = appView.definitionFor(superType);
+    return superClass == null || superClass.hasInstanceFieldsDirectlyOrIndirectly(appView);
+  }
+
   public boolean isValid(InternalOptions options) {
     assert verifyNoAbstractMethodsOnNonAbstractClasses(virtualMethods(), options);
     assert !isInterface() || virtualMethods().stream().noneMatch(DexEncodedMethod::isFinal);
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 62fc1a8..97da896 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -913,24 +913,22 @@
     return builder.build();
   }
 
-  public DexEncodedMethod toEmulateInterfaceLibraryMethod(
+  public static DexEncodedMethod toEmulateDispatchLibraryMethod(
+      DexType interfaceType,
       DexMethod newMethod,
       DexMethod companionMethod,
       DexMethod libraryMethod,
       List<Pair<DexType, DexMethod>> extraDispatchCases,
       AppView<?> appView) {
-    assert isDefaultMethod() || isStatic();
-    DexEncodedMethod.Builder builder = DexEncodedMethod.builder(this);
-    builder.setMethod(newMethod);
-    builder.accessFlags.setSynthetic();
-    builder.accessFlags.setStatic();
-    builder.accessFlags.unsetPrivate();
-    builder.accessFlags.setPublic();
-    builder.setCode(
+    MethodAccessFlags accessFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false);
+    CfCode code =
         new EmulateInterfaceSyntheticCfCodeProvider(
-                this.method.holder, companionMethod, libraryMethod, extraDispatchCases, appView)
-            .generateCfCode());
-    return builder.build();
+                interfaceType, companionMethod, libraryMethod, extraDispatchCases, appView)
+            .generateCfCode();
+    return new DexEncodedMethod(
+        newMethod, accessFlags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code);
   }
 
   public DexEncodedMethod toStaticForwardingBridge(DexClass holder, DexMethod newMethod) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 40c60ac..faad0e6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -230,6 +230,7 @@
   public final DexString consumerDescriptor = createString("Ljava/util/function/Consumer;");
   public final DexString runnableDescriptor = createString("Ljava/lang/Runnable;");
   public final DexString optionalDescriptor = createString("Ljava/util/Optional;");
+  public final DexString streamDescriptor = createString("Ljava/util/stream/Stream;");
   public final DexString arraysDescriptor = createString("Ljava/util/Arrays;");
 
   public final DexString throwableDescriptor = createString(throwableDescriptorString);
@@ -327,6 +328,7 @@
   public final DexType consumerType = createType(consumerDescriptor);
   public final DexType runnableType = createType(runnableDescriptor);
   public final DexType optionalType = createType(optionalDescriptor);
+  public final DexType streamType = createType(streamDescriptor);
 
   public final DexType runtimeExceptionType = createType(runtimeExceptionDescriptor);
   public final DexType throwableType = createType(throwableDescriptor);
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 e3d78cf..cc39610 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -246,6 +246,11 @@
     return isDoubleType() || isLongType();
   }
 
+  public boolean isD8R8SynthesizedLambdaClassType() {
+    String name = toSourceString();
+    return name.contains(LAMBDA_CLASS_NAME_PREFIX);
+  }
+
   public boolean isD8R8SynthesizedClassType() {
     String name = toSourceString();
     return name.contains(COMPANION_CLASS_NAME_SUFFIX)
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index cbce2db..f105f9c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -65,6 +65,14 @@
     return null;
   }
 
+  public DexValueString asDexValueString() {
+    return null;
+  }
+
+  public boolean isDexValueString() {
+    return false;
+  }
+
   public boolean isDexValueType() {
     return false;
   }
@@ -779,6 +787,16 @@
     }
 
     @Override
+    public DexValueString asDexValueString() {
+      return this;
+    }
+
+    @Override
+    public boolean isDexValueString() {
+      return true;
+    }
+
+    @Override
     public Object asAsmEncodedObject() {
       return value.toString();
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 8feed9b..edec3a9 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -941,14 +941,20 @@
     return parsingOptions;
   }
 
+  @Override
+  public boolean verifyNoInputReaders() {
+    assert context == null && application == null;
+    return true;
+  }
+
   private static boolean verifyNoReparseContext(DexClass owner) {
     for (DexEncodedMethod method : owner.virtualMethods()) {
       Code code = method.getCode();
-      assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+      assert code == null || code.verifyNoInputReaders();
     }
     for (DexEncodedMethod method : owner.directMethods()) {
       Code code = method.getCode();
-      assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+      assert code == null || code.verifyNoInputReaders();
     }
     return true;
   }
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 1f8343e..bdea265 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -5,6 +5,8 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
@@ -15,128 +17,67 @@
 
 public abstract class ResolutionResult {
 
+  /**
+   * Returns true if resolution succeeded *and* the resolved method has a known definition.
+   *
+   * <p>Note that {@code !isSingleResolution() && !isFailedResolution()} can be true. In that case
+   * that resolution has succeeded, but the definition of the resolved method is unknown. In
+   * particular this is the case for the clone() method on arrays.
+   */
+  public boolean isSingleResolution() {
+    return false;
+  }
+
+  /** Returns non-null if isSingleResolution() is true, otherwise null. */
+  public SingleResolutionResult asSingleResolution() {
+    return null;
+  }
+
+  /**
+   * Returns true if resolution failed.
+   *
+   * <p>Note the disclaimer in the doc of {@code isSingleResolution()}.
+   */
   public boolean isFailedResolution() {
     return false;
   }
 
+  /** Returns non-null if isFailedResolution() is true, otherwise null. */
   public FailedResolutionResult asFailedResolution() {
     return null;
   }
 
-  public abstract DexEncodedMethod getSingleTarget();
+  /** Short-hand to get the single resolution method if resolution finds it, null otherwise. */
+  public final DexEncodedMethod getSingleTarget() {
+    return isSingleResolution() ? asSingleResolution().getResolvedMethod() : null;
+  }
 
-  public abstract boolean hasSingleTarget();
+  public abstract boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo);
+
+  public abstract boolean isAccessibleForVirtualDispatchFrom(
+      DexProgramClass context, AppInfoWithSubtyping appInfo);
 
   public abstract boolean isValidVirtualTarget(InternalOptions options);
 
   public abstract boolean isValidVirtualTargetForDynamicDispatch();
 
-  public Set<DexEncodedMethod> lookupVirtualDispatchTargets(
+  /** Lookup the single target of an invoke-super on this resolution result if possible. */
+  public abstract DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo);
+
+  public final Set<DexEncodedMethod> lookupVirtualDispatchTargets(
       boolean isInterface, AppInfoWithSubtyping appInfo) {
     return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo);
   }
 
-  // TODO(b/140204899): Leverage refined receiver type if available.
-  public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
-    assert isValidVirtualTarget(appInfo.app().options);
-    // First add the target for receiver type method.type.
-    DexEncodedMethod encodedMethod = getSingleTarget();
-    Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
-    // Add all matching targets from the subclass hierarchy.
-    DexMethod method = encodedMethod.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)) {
-      DexClass clazz = appInfo.definitionFor(type);
-      if (!clazz.isInterface()) {
-        ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method);
-        DexEncodedMethod target = methods.getSingleTarget();
-        if (target != null && target.isVirtualMethod()) {
-          result.add(target);
-        }
-      }
-    }
-    return result;
-  }
+  public abstract Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo);
 
-  // TODO(b/140204899): Leverage refined receiver type if available.
-  public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
-    assert isValidVirtualTarget(appInfo.app().options);
-    Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
-    if (hasSingleTarget()) {
-      // 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);
-        }
-      }
-    }
+  public abstract Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo);
 
-    DexEncodedMethod encodedMethod = getSingleTarget();
-    DexMethod method = encodedMethod.method;
-    Consumer<DexEncodedMethod> addIfNotAbstract =
-        m -> {
-          if (!m.accessFlags.isAbstract()) {
-            result.add(m);
-          }
-        };
-    // Default methods are looked up when looking at a specific subtype that does not override
-    // them.
-    // Otherwise, we would look up default methods that are actually never used. However, we have
-    // to
-    // add bridge methods, otherwise we can remove a bridge that will be used.
-    Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
-        m -> {
-          if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
-            result.add(m);
-          }
-        };
-
-    // 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)) {
-      DexClass clazz = appInfo.definitionFor(type);
-      if (clazz.isInterface()) {
-        ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
-        if (targetMethods.hasSingleTarget()) {
-          addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
-        }
-      } else {
-        ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
-        if (targetMethods.hasSingleTarget()) {
-          addIfNotAbstract.accept(targetMethods.getSingleTarget());
-        }
-      }
-    }
-    return result;
-  }
-
+  /** Result for a resolution that succeeds with a known declaration/definition. */
   public static class SingleResolutionResult extends ResolutionResult {
-    final DexEncodedMethod resolutionTarget;
+    private final DexClass initialResolutionHolder;
+    private final DexClass resolvedHolder;
+    private final DexEncodedMethod resolvedMethod;
 
     public static boolean isValidVirtualTarget(InternalOptions options, DexEncodedMethod target) {
       return options.canUseNestBasedAccess()
@@ -144,55 +85,242 @@
           : target.isVirtualMethod();
     }
 
-    public SingleResolutionResult(DexEncodedMethod resolutionTarget) {
-      assert resolutionTarget != null;
-      this.resolutionTarget = resolutionTarget;
+    public SingleResolutionResult(
+        DexClass initialResolutionHolder,
+        DexClass resolvedHolder,
+        DexEncodedMethod resolvedMethod) {
+      assert initialResolutionHolder != null;
+      assert resolvedHolder != null;
+      assert resolvedMethod != null;
+      assert resolvedHolder.type == resolvedMethod.method.holder;
+      this.resolvedHolder = resolvedHolder;
+      this.resolvedMethod = resolvedMethod;
+      this.initialResolutionHolder = initialResolutionHolder;
+    }
+
+    public DexClass getResolvedHolder() {
+      return resolvedHolder;
+    }
+
+    public DexEncodedMethod getResolvedMethod() {
+      return resolvedMethod;
+    }
+
+    @Override
+    public boolean isSingleResolution() {
+      return true;
+    }
+
+    @Override
+    public SingleResolutionResult asSingleResolution() {
+      return this;
+    }
+
+    @Override
+    public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return AccessControl.isMethodAccessible(
+          resolvedMethod, initialResolutionHolder, context, appInfo);
+    }
+
+    @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()));
     }
 
     @Override
     public boolean isValidVirtualTarget(InternalOptions options) {
-      return isValidVirtualTarget(options, resolutionTarget);
+      return isValidVirtualTarget(options, resolvedMethod);
     }
 
     @Override
     public boolean isValidVirtualTargetForDynamicDispatch() {
-      return resolutionTarget.isVirtualMethod();
+      return resolvedMethod.isVirtualMethod();
+    }
+
+    /**
+     * Lookup super method following the super chain from the holder of {@code method}.
+     *
+     * <p>This method will resolve the method on the holder of {@code method} and only return a
+     * non-null value if the result of resolution was an instance (i.e. non-static) method.
+     *
+     * <p>Additionally, this will also verify that the invoke super is valid, i.e., it is on the
+     * same type or a super type of the current context. The spec says that it has invoke super
+     * semantics, if the type is a supertype of the current class. If it is the same or a subtype,
+     * it has invoke direct semantics. The latter case is illegal, so we map it to a super call
+     * here. In R8, we abort at a later stage (see. See also <a href=
+     * "https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial" </a>
+     * for invokespecial dispatch and <a href="https://docs.oracle.com/javase/specs/jvms/"
+     * "se7/html/jvms-4.html#jvms-4.10.1.9.invokespecial"</a> for verification requirements. In
+     * particular, the requirement isAssignable(class(CurrentClassName, L), class(MethodClassName,
+     * L)). com.android.tools.r8.cf.code.CfInvoke#isInvokeSuper(DexType)}.
+     *
+     * @param context the class the invoke is contained in, i.e., the holder of the caller.
+     * @param appInfo Application info.
+     * @return The actual target for the invoke-super or {@code null} if none found.
+     */
+    @Override
+    public DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo) {
+      DexMethod method = resolvedMethod.method;
+      // TODO(b/145775365): Check the requirements for an invoke-special to a protected method.
+      // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial
+
+      if (appInfo.hasSubtyping()
+          && !appInfo.withSubtyping().isSubtype(context, initialResolutionHolder.type)) {
+        DexClass contextClass = appInfo.definitionFor(context);
+        throw new CompilationError(
+            "Illegal invoke-super to " + method.toSourceString() + " from class " + context,
+            contextClass != null ? contextClass.getOrigin() : Origin.unknown());
+      }
+
+      // According to
+      // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial, use
+      // the "symbolic reference" if the "symbolic reference" does not name a class.
+      // TODO(b/145775365): This looks like the exact opposite of what the spec says, second item is
+      //  - is-class(sym-ref) => is-super(sym-ref, current-class)
+      //  this implication trivially holds for !is-class(sym-ref) == is-inteface(sym-ref), thus
+      //  the resolution should specifically *not* use the "symbolic reference".
+      if (initialResolutionHolder.isInterface()) {
+        // TODO(b/145775365): This does not consider a static method!
+        return appInfo.resolveMethodOnInterface(initialResolutionHolder, method).getSingleTarget();
+      }
+      // Then, resume on the search, but this time, starting from the holder of the caller.
+      DexClass contextClass = appInfo.definitionFor(context);
+      if (contextClass == null || contextClass.superType == null) {
+        return null;
+      }
+      SingleResolutionResult resolution =
+          appInfo.resolveMethodOnClass(contextClass.superType, method).asSingleResolution();
+      return resolution != null && !resolution.resolvedMethod.isStatic()
+          ? resolution.resolvedMethod
+          : null;
     }
 
     @Override
-    public DexEncodedMethod getSingleTarget() {
-      return resolutionTarget;
-    }
-
-    @Override
-    public boolean hasSingleTarget() {
-      return true;
-    }
-  }
-
-  public abstract static class EmptyResult extends ResolutionResult {
-
-    @Override
-    public DexEncodedMethod getSingleTarget() {
-      return null;
-    }
-
-    @Override
-    public boolean hasSingleTarget() {
-      return false;
-    }
-
-    @Override
+    // TODO(b/140204899): Leverage refined receiver type if available.
     public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+      assert isValidVirtualTarget(appInfo.app().options);
+      // First add the target for receiver type method.type.
+      DexEncodedMethod encodedMethod = getSingleTarget();
+      Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
+      // Add all matching targets from the subclass hierarchy.
+      DexMethod method = encodedMethod.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)) {
+        DexClass clazz = appInfo.definitionFor(type);
+        if (!clazz.isInterface()) {
+          ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method);
+          DexEncodedMethod target = methods.getSingleTarget();
+          if (target != null && target.isVirtualMethod()) {
+            result.add(target);
+          }
+        }
+      }
+      return result;
+    }
+
+    @Override
+    // TODO(b/140204899): Leverage refined receiver type if available.
+    public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+      assert isValidVirtualTarget(appInfo.app().options);
+      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);
+          }
+        }
+      }
+
+      DexEncodedMethod encodedMethod = getSingleTarget();
+      DexMethod method = encodedMethod.method;
+      Consumer<DexEncodedMethod> addIfNotAbstract =
+          m -> {
+            if (!m.accessFlags.isAbstract()) {
+              result.add(m);
+            }
+          };
+      // Default methods are looked up when looking at a specific subtype that does not override
+      // them.
+      // Otherwise, we would look up default methods that are actually never used. However, we have
+      // to
+      // add bridge methods, otherwise we can remove a bridge that will be used.
+      Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
+          m -> {
+            if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
+              result.add(m);
+            }
+          };
+
+      // 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)) {
+        DexClass clazz = appInfo.definitionFor(type);
+        if (clazz.isInterface()) {
+          ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
+          if (targetMethods.isSingleResolution()) {
+            addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
+          }
+        } else {
+          ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
+          if (targetMethods.isSingleResolution()) {
+            addIfNotAbstract.accept(targetMethods.getSingleTarget());
+          }
+        }
+      }
+      return result;
+    }
+  }
+
+  abstract static class EmptyResult extends ResolutionResult {
+
+    @Override
+    public final DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo) {
       return null;
     }
 
     @Override
-    public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+    public final Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+      return null;
+    }
+
+    @Override
+    public final Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
       return null;
     }
   }
 
+  /** Singleton result for the special case resolving the array clone() method. */
   public static class ArrayCloneMethodResult extends EmptyResult {
 
     static final ArrayCloneMethodResult INSTANCE = new ArrayCloneMethodResult();
@@ -202,6 +330,17 @@
     }
 
     @Override
+    public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return true;
+    }
+
+    @Override
+    public boolean isAccessibleForVirtualDispatchFrom(
+        DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return true;
+    }
+
+    @Override
     public boolean isValidVirtualTarget(InternalOptions options) {
       return true;
     }
@@ -212,6 +351,7 @@
     }
   }
 
+  /** Base class for all types of failed resolutions. */
   public abstract static class FailedResolutionResult extends EmptyResult {
 
     @Override
@@ -229,6 +369,17 @@
     }
 
     @Override
+    public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return false;
+    }
+
+    @Override
+    public boolean isAccessibleForVirtualDispatchFrom(
+        DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return false;
+    }
+
+    @Override
     public boolean isValidVirtualTarget(InternalOptions options) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index e5ace97..1049fb8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -336,7 +336,7 @@
       DexMethod method = instruction.getInvokedMethod();
       ResolutionResult resolutionResult =
           appView.appInfo().resolveMethodOnInterface(method.holder, method);
-      if (!resolutionResult.hasSingleTarget()) {
+      if (!resolutionResult.isSingleResolution()) {
         return false;
       }
       DexType holder = resolutionResult.getSingleTarget().method.holder;
@@ -394,7 +394,7 @@
       }
       ResolutionResult resolutionResult =
           appView.appInfo().resolveMethod(superType, method, instruction.itf);
-      if (!resolutionResult.hasSingleTarget()) {
+      if (!resolutionResult.isSingleResolution()) {
         return false;
       }
       DexType holder = resolutionResult.getSingleTarget().method.holder;
@@ -430,7 +430,7 @@
       DexMethod method = instruction.getInvokedMethod();
       ResolutionResult resolutionResult =
           appView.appInfo().resolveMethodOnClass(method.holder, method);
-      if (!resolutionResult.hasSingleTarget()) {
+      if (!resolutionResult.isSingleResolution()) {
         return false;
       }
       DexType holder = resolutionResult.getSingleTarget().method.holder;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 0605623..9fcee4a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.ir.analysis;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.IRCode;
@@ -16,6 +19,7 @@
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
+import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
@@ -284,6 +288,12 @@
       return false;
     }
 
+    NewInstance newInstance = value.definition.asNewInstance();
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(newInstance.clazz));
+    if (clazz == null) {
+      return false;
+    }
+
     // Find the single constructor invocation.
     InvokeMethod constructorInvoke = null;
     for (Instruction instruction : value.uniqueUsers()) {
@@ -319,24 +329,26 @@
       return false;
     }
 
-    InstanceInitializerInfo initializerInfo =
-        constructor.getOptimizationInfo().getInstanceInitializerInfo();
-    if (initializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
-      return false;
-    }
-
-    // Check that none of the arguments to the constructor depend on the environment.
-    for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
-      Value argument = constructorInvoke.arguments().get(i);
-      if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
+    if (clazz.hasInstanceFieldsDirectlyOrIndirectly(appView)) {
+      InstanceInitializerInfo initializerInfo =
+          constructor.getOptimizationInfo().getInstanceInitializerInfo();
+      if (initializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
         return false;
       }
-    }
 
-    // Finally, check that the object does not escape.
-    if (valueMayBeMutatedBeforeMethodExit(
-        value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
-      return false;
+      // Check that none of the arguments to the constructor depend on the environment.
+      for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
+        Value argument = constructorInvoke.arguments().get(i);
+        if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
+          return false;
+        }
+      }
+
+      // Finally, check that the object does not escape.
+      if (valueMayBeMutatedBeforeMethodExit(
+          value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
+        return false;
+      }
     }
 
     if (assumedNotToDependOnEnvironment.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index 0ac6de7..bc13aa1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -27,8 +27,9 @@
     fields.add(field);
   }
 
-  public void addAll(ConcreteMutableFieldSet other) {
+  public ConcreteMutableFieldSet addAll(ConcreteMutableFieldSet other) {
     fields.addAll(other.fields);
+    return this;
   }
 
   Set<DexEncodedField> getFields() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 484c4e3..e7d2ab1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -110,7 +110,7 @@
     if (refinedReceiverType != staticReceiverType) {
       ResolutionResult refinedResolution =
           appView.appInfo().resolveMethod(refinedReceiverType, method);
-      if (refinedResolution.hasSingleTarget()) {
+      if (refinedResolution.isSingleResolution()) {
         DexEncodedMethod refinedTarget = refinedResolution.getSingleTarget();
         Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
         for (DexEncodedMethod target : targets) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index d9d65e5..407742e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -175,7 +175,7 @@
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     ResolutionResult finalizeResolutionResult =
         appView.appInfo().resolveMethod(clazz, dexItemFactory.objectMethods.finalize);
-    if (finalizeResolutionResult.hasSingleTarget()) {
+    if (finalizeResolutionResult.isSingleResolution()) {
       DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().method;
       if (finalizeMethod != dexItemFactory.enumMethods.finalize
           && finalizeMethod != dexItemFactory.objectMethods.finalize) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 2f0911d..16fc789 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexMethod;
@@ -16,11 +17,14 @@
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Deque;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -395,4 +399,31 @@
     }
     return result;
   }
+
+  @Override
+  public TypeLatticeElement getDynamicUpperBoundType(
+      AppView<? extends AppInfoWithSubtyping> appView) {
+    Set<Phi> reachablePhis = SetUtils.newIdentityHashSet(this);
+    Deque<Phi> worklist = DequeUtils.newArrayDeque(this);
+    while (!worklist.isEmpty()) {
+      Phi phi = worklist.removeFirst();
+      assert reachablePhis.contains(phi);
+      for (Value operand : phi.getOperands()) {
+        Phi candidate = operand.getAliasedValue().asPhi();
+        if (candidate != null && reachablePhis.add(candidate)) {
+          worklist.addLast(candidate);
+        }
+      }
+    }
+    Set<Value> visitedOperands = Sets.newIdentityHashSet();
+    TypeLatticeElement result = TypeLatticeElement.BOTTOM;
+    for (Phi phi : reachablePhis) {
+      for (Value operand : phi.getOperands()) {
+        if (!operand.getAliasedValue().isPhi() && visitedOperands.add(operand)) {
+          result = result.join(operand.getDynamicUpperBoundType(appView), appView);
+        }
+      }
+    }
+    return result;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index dca6174..d6372b5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -4,6 +4,13 @@
 package com.android.tools.r8.ir.code;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -888,6 +895,27 @@
     return isArgument;
   }
 
+  public boolean onlyDependsOnArgument() {
+    if (isPhi()) {
+      return false;
+    }
+    switch (definition.opcode()) {
+      case ARGUMENT:
+      case CONST_CLASS:
+      case CONST_NUMBER:
+      case CONST_STRING:
+      case DEX_ITEM_BASED_CONST_STRING:
+        // Constants don't depend on anything.
+        return true;
+      case CHECK_CAST:
+        return definition.asCheckCast().object().onlyDependsOnArgument();
+      case INSTANCE_OF:
+        return definition.asInstanceOf().value().onlyDependsOnArgument();
+      default:
+        return false;
+    }
+  }
+
   public int computeArgumentPosition(IRCode code) {
     assert isArgument;
     int position = 0;
@@ -1086,6 +1114,14 @@
 
   public TypeLatticeElement getDynamicUpperBoundType(
       AppView<? extends AppInfoWithSubtyping> appView) {
+    Value root = getAliasedValue();
+    if (root.isPhi()) {
+      assert getSpecificAliasedValue(
+              value -> !value.isPhi() && value.definition.isAssumeDynamicType())
+          == null;
+      return root.getDynamicUpperBoundType(appView);
+    }
+
     // Try to find an alias of the receiver, which is defined by an instruction of the type
     // Assume<DynamicTypeAssumption>.
     Value aliasedValue =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index c4f9072..06f1f58 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -9,8 +9,12 @@
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
 import com.android.tools.r8.ir.conversion.CallSiteInformation.CallGraphBasedCallSiteInformation;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Iterator;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * Call graph representation.
@@ -172,4 +176,32 @@
         ? new CallGraphBasedCallSiteInformation(appView, this)
         : CallSiteInformation.empty();
   }
+
+  public boolean isEmpty() {
+    return nodes.isEmpty();
+  }
+
+  public Set<DexEncodedMethod> extractLeaves() {
+    return extractNodes(Node::isLeaf, Node::cleanCallersForRemoval);
+  }
+
+  public Set<DexEncodedMethod> extractRoots() {
+    return extractNodes(Node::isRoot, Node::cleanCalleesForRemoval);
+  }
+
+  private Set<DexEncodedMethod> extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
+    Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+    Set<Node> removed = Sets.newIdentityHashSet();
+    Iterator<Node> nodeIterator = nodes.iterator();
+    while (nodeIterator.hasNext()) {
+      Node node = nodeIterator.next();
+      if (predicate.test(node)) {
+        result.add(node.method);
+        nodeIterator.remove();
+        removed.add(node);
+      }
+    }
+    removed.forEach(clean);
+    return result;
+  }
 }
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 45be243..5041e1d 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
@@ -296,7 +296,7 @@
               : null;
       this.lambdaMerger =
           options.enableLambdaMerging ? new LambdaMerger(appViewWithLiveness) : null;
-      this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, lambdaRewriter);
+      this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness);
       this.inliner =
           new Inliner(appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter);
       this.outliner = new Outliner(appViewWithLiveness);
@@ -807,11 +807,13 @@
     return builder.build();
   }
 
-  private void waveStart(Collection<DexEncodedMethod> wave) {
+  private void waveStart(Collection<DexEncodedMethod> wave, ExecutorService executorService)
+      throws ExecutionException {
     onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
 
     if (lambdaRewriter != null) {
-      wave.forEach(method -> lambdaRewriter.synthesizeLambdaClassesFor(method, lensCodeRewriter));
+      lambdaRewriter.synthesizeLambdaClassesForWave(
+          wave, executorService, delayedOptimizationFeedback, lensCodeRewriter);
     }
   }
 
@@ -975,10 +977,25 @@
     for (DexProgramClass clazz : classes) {
       clazz.forEachMethod(methods::add);
     }
-    // Process the generated class, but don't apply any outlining.
     processMethodsConcurrently(methods, executorService);
   }
 
+  public void optimizeSynthesizedLambdaClasses(
+      Collection<DexProgramClass> classes, ExecutorService executorService)
+      throws ExecutionException {
+    assert appView.enableWholeProgramOptimizations();
+    Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
+    for (DexProgramClass clazz : classes) {
+      clazz.forEachMethod(methods::add);
+    }
+    LambdaMethodProcessor processor =
+        new LambdaMethodProcessor(appView.withLiveness(), methods, executorService, timing);
+    processor.forEachMethod(
+        method -> processMethod(method, delayedOptimizationFeedback, processor),
+        delayedOptimizationFeedback::updateVisibleOptimizationInfo,
+        executorService);
+  }
+
   public void optimizeSynthesizedMethod(DexEncodedMethod method) {
     if (!method.isProcessed()) {
       // Process the generated method, but don't apply any outlining.
@@ -1111,10 +1128,11 @@
         lensCodeRewriter.rewrite(code, method);
       } else {
         assert appView.graphLense().isIdentityLense();
-        if (lambdaRewriter != null && options.testing.desugarLambdasThroughLensCodeRewriter()) {
-          lambdaRewriter.desugarLambdas(method, code);
-          assert code.isConsistentSSA();
-        }
+      }
+
+      if (lambdaRewriter != null) {
+        lambdaRewriter.desugarLambdas(method, code);
+        assert code.isConsistentSSA();
       }
     }
 
@@ -1277,12 +1295,6 @@
 
     stringConcatRewriter.desugarStringConcats(method.method, code);
 
-    if (options.testing.desugarLambdasThroughLensCodeRewriter()) {
-      assert !options.enableDesugaring || lambdaRewriter.verifyNoLambdasToDesugar(code);
-    } else if (lambdaRewriter != null) {
-      lambdaRewriter.desugarLambdas(method, code);
-      assert code.isConsistentSSA();
-    }
     previous = printMethod(code, "IR after lambda desugaring (SSA)", previous);
 
     assert code.verifyTypes(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java
new file mode 100644
index 0000000..a6a9b94
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.IROrdering;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.Timing;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+class LambdaMethodProcessor implements MethodProcessor {
+
+  private final Deque<Collection<DexEncodedMethod>> waves;
+  private Collection<DexEncodedMethod> wave;
+
+  LambdaMethodProcessor(
+      AppView<AppInfoWithLiveness> appView,
+      Set<DexEncodedMethod> methods,
+      ExecutorService executorService,
+      Timing timing)
+      throws ExecutionException {
+    CallGraph callGraph =
+        new PartialCallGraphBuilder(appView, methods).build(executorService, timing);
+    this.waves = createWaves(appView, callGraph);
+  }
+
+  @Override
+  public Phase getPhase() {
+    return Phase.LAMBDA_PROCESSING;
+  }
+
+  private Deque<Collection<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
+    IROrdering shuffle = appView.options().testing.irOrdering;
+    Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
+    while (!callGraph.isEmpty()) {
+      waves.addLast(shuffle.order(callGraph.extractLeaves()));
+    }
+    return waves;
+  }
+
+  @Override
+  public boolean isProcessedConcurrently(DexEncodedMethod method) {
+    return wave.contains(method);
+  }
+
+  <E extends Exception> void forEachMethod(
+      ThrowingConsumer<DexEncodedMethod, E> consumer,
+      Action waveDone,
+      ExecutorService executorService)
+      throws ExecutionException {
+    while (!waves.isEmpty()) {
+      wave = waves.removeFirst();
+      assert wave.size() > 0;
+      ThreadUtils.processItems(wave, consumer, executorService);
+      waveDone.execute();
+    }
+  }
+}
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 21f800c..bf556c8 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
@@ -60,7 +60,6 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.logging.Log;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -77,11 +76,9 @@
   private final AppView<? extends AppInfoWithSubtyping> appView;
 
   private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
-  private final LambdaRewriter lambdaRewriter;
 
-  LensCodeRewriter(AppView<? extends AppInfoWithSubtyping> appView, LambdaRewriter lambdaRewriter) {
+  LensCodeRewriter(AppView<? extends AppInfoWithSubtyping> appView) {
     this.appView = appView;
-    this.lambdaRewriter = lambdaRewriter;
   }
 
   private Value makeOutValue(Instruction insn, IRCode code) {
@@ -128,13 +125,6 @@
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
-          if (lambdaRewriter != null
-              && appView.options().testing.desugarLambdasThroughLensCodeRewriter()) {
-            Instruction previous = iterator.peekPrevious();
-            assert previous.isInvokeCustom();
-            lambdaRewriter.desugarLambda(
-                method.method.holder, iterator, previous.asInvokeCustom(), code);
-          }
         } else if (current.isConstMethodHandle()) {
           DexMethodHandle handle = current.asConstMethodHandle().getValue();
           DexMethodHandle newHandle = rewriteDexMethodHandle(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 0f6bd4f..7d4a203 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
@@ -59,7 +59,8 @@
 
   void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility);
 
-  void setInitializerInfo(DexEncodedMethod method, InitializerInfo info);
+  void setInstanceInitializerInfo(
+      DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo);
 
   void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index b2bf648..d6e2ff3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -9,6 +9,7 @@
 
   enum Phase {
     ONE_TIME,
+    LAMBDA_PROCESSING,
     PRIMARY,
     POST
   }
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 5107b18..9ecc62d 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.logging.Log;
@@ -17,17 +16,14 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
 
 class PostMethodProcessor implements MethodProcessor {
 
@@ -106,11 +102,9 @@
     IROrdering shuffle = appView.options().testing.irOrdering;
     Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
 
-    Set<Node> nodes = callGraph.nodes;
     int waveCount = 1;
-    while (!nodes.isEmpty()) {
-      Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
-      extractRoots(nodes, n -> wave.add(n.method));
+    while (!callGraph.isEmpty()) {
+      Set<DexEncodedMethod> wave = callGraph.extractRoots();
       waves.addLast(shuffle.order(wave));
       if (Log.ENABLED && Log.isLoggingEnabledFor(PostMethodProcessor.class)) {
         Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
@@ -120,25 +114,6 @@
     return waves;
   }
 
-  /**
-   * Extract the next set of roots (nodes with an incoming call degree of 0) if any.
-   *
-   * <p>All nodes in the graph are extracted if called repeatedly until null is returned.
-   */
-  static void extractRoots(Set<Node> nodes, Consumer<Node> fn) {
-    Set<Node> removed = Sets.newIdentityHashSet();
-    Iterator<Node> nodeIterator = nodes.iterator();
-    while (nodeIterator.hasNext()) {
-      Node node = nodeIterator.next();
-      if (node.isRoot()) {
-        fn.accept(node);
-        nodeIterator.remove();
-        removed.add(node);
-      }
-    }
-    removed.forEach(Node::cleanCalleesForRemoval);
-  }
-
   @Override
   public boolean isProcessedConcurrently(DexEncodedMethod method) {
     return wave != null && wave.contains(method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 1787587..be529f4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -31,6 +31,12 @@
  */
 class PrimaryMethodProcessor implements MethodProcessor {
 
+  interface WaveStartAction {
+
+    void notifyWaveStart(Collection<DexEncodedMethod> wave, ExecutorService executorService)
+        throws ExecutionException;
+  }
+
   private final CallSiteInformation callSiteInformation;
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
   private final Deque<Collection<DexEncodedMethod>> waves;
@@ -130,14 +136,14 @@
    */
   <E extends Exception> void forEachMethod(
       ThrowingConsumer<DexEncodedMethod, E> consumer,
-      Consumer<Collection<DexEncodedMethod>> waveStart,
+      WaveStartAction waveStartAction,
       Action waveDone,
       ExecutorService executorService)
       throws ExecutionException {
     while (!waves.isEmpty()) {
       wave = waves.removeFirst();
       assert wave.size() > 0;
-      waveStart.accept(wave);
+      waveStartAction.notifyWaveStart(wave, executorService);
       ThreadUtils.processItems(wave, consumer, executorService);
       waveDone.execute();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java
new file mode 100644
index 0000000..20fc11c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceDebugExtensionRewriter.java
@@ -0,0 +1,205 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Multimaps;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+import java.util.function.Predicate;
+
+public class SourceDebugExtensionRewriter {
+
+  private static final String SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX = "$i$f$";
+
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
+
+  public SourceDebugExtensionRewriter(AppView<?> appView) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+  }
+
+  public SourceDebugExtensionRewriter analyze(Predicate<DexProgramClass> shouldProcess) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (!shouldProcess.test(clazz)) {
+        continue;
+      }
+      DexAnnotation sourceDebug =
+          clazz.annotations.getFirstMatching(factory.annotationSourceDebugExtension);
+      if (sourceDebug == null || sourceDebug.annotation.elements.length != 1) {
+        continue;
+      }
+      DexValueString dexValueString = sourceDebug.annotation.elements[0].value.asDexValueString();
+      if (dexValueString == null) {
+        continue;
+      }
+      Result parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
+      if (parsedData == null) {
+        continue;
+      }
+      for (DexEncodedMethod method : clazz.methods()) {
+        if (method.getCode().isCfCode()) {
+          processMethod(method, parsedData);
+        }
+      }
+    }
+    return this;
+  }
+
+  private static class Context {
+
+    private Position currentPosition = null;
+    private LocalVariableInfo localVariableInliningInfoEntry = null;
+    private final Stack<CfLabel> endRangeLabels = new Stack<>();
+    private final List<CfInstruction> resultingList;
+    private final ImmutableListMultimap<CfLabel, LocalVariableInfo> localVariableInfoStartMap;
+    private int lastPosition = -1;
+
+    Context(
+        int initialSize,
+        ImmutableListMultimap<CfLabel, LocalVariableInfo> localVariableInfoStartMap) {
+      this.resultingList = new ArrayList<>(initialSize);
+      this.localVariableInfoStartMap = localVariableInfoStartMap;
+    }
+
+    String getInlinedFunctionName() {
+      return localVariableInliningInfoEntry
+          .getLocal()
+          .name
+          .toString()
+          .substring(SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX.length());
+    }
+  }
+
+  private void processMethod(DexEncodedMethod method, Result parsedData) {
+    CfCode cfCode = method.getCode().asCfCode();
+    Context context =
+        new Context(
+            cfCode.getInstructions().size() + parsedData.getPositions().size(),
+            Multimaps.index(cfCode.getLocalVariables(), LocalVariableInfo::getStart));
+    for (CfInstruction instruction : cfCode.getInstructions()) {
+      if (instruction.isLabel()) {
+        handleLabel(context, instruction.asLabel());
+      } else if (instruction.isPosition()
+          && (context.currentPosition != null || context.localVariableInliningInfoEntry != null)) {
+        handlePosition(context, instruction.asPosition(), parsedData);
+      } else {
+        context.resultingList.add(instruction);
+      }
+    }
+    cfCode.instructions = context.resultingList;
+  }
+
+  private void handleLabel(Context context, CfLabel label) {
+    ImmutableList<LocalVariableInfo> localVariableInfos =
+        context.localVariableInfoStartMap.get(label);
+    if (localVariableInfos != null) {
+      LocalVariableInfo newLocalVariableInliningInfo = null;
+      for (LocalVariableInfo localVariableInfo : localVariableInfos) {
+        String localVariableName = localVariableInfo.getLocal().name.toString();
+        if (!localVariableName.startsWith(SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX)) {
+          continue;
+        }
+        // Only one synthetic inlining label for a position should exist.
+        assert newLocalVariableInliningInfo == null;
+        newLocalVariableInliningInfo = localVariableInfo;
+      }
+      context.localVariableInliningInfoEntry = newLocalVariableInliningInfo;
+    }
+    while (!context.endRangeLabels.empty() && context.endRangeLabels.peek() == label) {
+      // The inlined range is ending here. Multiple inline ranges can end at the same label.
+      assert context.currentPosition != null;
+      context.currentPosition = context.currentPosition.callerPosition;
+      context.endRangeLabels.pop();
+    }
+    // Ensure endRangeLabels are in sync with the current position.
+    assert !context.endRangeLabels.empty() || context.currentPosition == null;
+    context.resultingList.add(label);
+  }
+
+  private void handlePosition(Context context, CfPosition position, Result parsedData) {
+    if (context.localVariableInliningInfoEntry != null) {
+      // This is potentially a new inlining frame.
+      KotlinSourceDebugExtensionParser.Position parsedInlinePosition =
+          parsedData.getPositions().get(position.getPosition().line);
+      if (parsedInlinePosition != null) {
+        String descriptor = "L" + parsedInlinePosition.getSource().getPath() + ";";
+        if (DescriptorUtils.isClassDescriptor(descriptor)) {
+          // This is a new inline function. Build up the inlining information from the parsed data
+          // and the local variable table.
+          DexType sourceHolder = factory.createType(descriptor);
+          final String inlinee = context.getInlinedFunctionName();
+          // TODO(b/145904809): See if we can find the inline function.
+          DexMethod syntheticExistingMethod =
+              factory.createMethod(
+                  sourceHolder,
+                  factory.createProto(factory.voidType),
+                  factory.createString(inlinee));
+          context.currentPosition =
+              new Position(
+                  parsedInlinePosition.getRange().from,
+                  null,
+                  syntheticExistingMethod,
+                  context.currentPosition);
+          context.endRangeLabels.push(context.localVariableInliningInfoEntry.getEnd());
+          context.lastPosition = position.getPosition().line;
+        }
+      }
+      context.localVariableInliningInfoEntry = null;
+    }
+    if (context.currentPosition != null) {
+      // We have a line-entry in a mapped range. Make sure to increment the index according to
+      // the delta in the inlined source.
+      Position currentPosition = context.currentPosition;
+      assert context.lastPosition > -1;
+      int delta = position.getPosition().line - context.lastPosition;
+      context.currentPosition =
+          new Position(
+              context.currentPosition.line + delta,
+              null,
+              currentPosition.method,
+              currentPosition.callerPosition);
+      // Append the original line index as the current caller context.
+      context.resultingList.add(
+          new CfPosition(
+              position.getLabel(),
+              appendAsOuterMostCaller(context.currentPosition, position.getPosition())));
+    } else {
+      context.resultingList.add(position);
+    }
+  }
+
+  private Position appendAsOuterMostCaller(Position position, Position callerPosition) {
+    if (position == null) {
+      return callerPosition;
+    } else {
+      return new Position(
+          position.line,
+          position.file,
+          position.method,
+          appendAsOuterMostCaller(position.callerPosition, callerPosition));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 41995ce..939d33a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
@@ -24,9 +25,11 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -38,16 +41,19 @@
 import com.android.tools.r8.ir.desugar.backports.LongMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.NumericMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites;
+import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
+import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -105,30 +111,44 @@
           continue;
         }
         // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
-        DexEncodedMethod dexEncodedMethod =
-            quickLookUp(invoke.getInvokedMethod());
-        if (dexEncodedMethod == null) {
+        ResolutionResult resolutionResult =
+            appView
+                .appInfo()
+                .resolveMethod(invoke.getInvokedMethod().holder, invoke.getInvokedMethod());
+        if (resolutionResult.isFailedResolution()) {
           continue;
         }
-        provider = getMethodProviderOrNull(dexEncodedMethod.method);
+        DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+        assert singleTarget != null;
+        provider = getMethodProviderOrNull(singleTarget.method);
         if (provider == null) {
           continue;
         }
+      }
 
-        // Since we are rewriting a virtual method into a static invoke in this case, the look-up
-        // logic gets confused. Final methods rewritten in such a way are always or invokes from a
-        // library class are rewritten into the static invoke, which is correct. However,
-        // overrides of the programmer are currently disabled. We still rewrite everything to make
-        // basic cases work.
-        // TODO(b/142846107): Support overrides of retarget virtual methods by uncommenting the
-        // following and implementing doSomethingSmart().
-
-        // DexClass receiverType = appView.definitionFor(invoke.getInvokedMethod().holder);
-        // if (!(dexEncodedMethod.isFinal()
-        //     || (receiverType != null && receiverType.isLibraryClass()))) {
-        //   doSomethingSmart();
-        //   continue;
-        // }
+      // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
+      // infinite loops. We do direct resolution. This is a very uncommon case.
+      if (invoke.isInvokeSuper()) {
+        DexEncodedMethod dexEncodedMethod =
+            appView
+                .appInfo()
+                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.method.holder);
+        if (!dexEncodedMethod.isFinal()) { // Final methods can be rewritten as a normal invoke.
+          Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+              appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+          Map<DexType, DexType> typeMap = retargetCoreLibMember.get(dexEncodedMethod.method.name);
+          if (typeMap != null && typeMap.containsKey(dexEncodedMethod.method.holder)) {
+            DexMethod retargetMethod =
+                factory.createMethod(
+                    typeMap.get(dexEncodedMethod.method.holder),
+                    factory.prependTypeToProto(
+                        dexEncodedMethod.method.holder, dexEncodedMethod.method.proto),
+                    dexEncodedMethod.method.name);
+            iterator.replaceCurrentInstruction(
+                new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
+          }
+          continue;
+        }
       }
 
       provider.rewriteInvoke(invoke, iterator, code, appView);
@@ -141,31 +161,6 @@
     }
   }
 
-  private DexEncodedMethod quickLookUp(DexMethod method) {
-    // Since retargeting cannot be on interface, we do a quick look-up excluding interfaces.
-    // On R8 resolution is immediate, on d8 it may look-up.
-    DexClass current = appView.definitionFor(method.holder);
-    if (current == null) {
-      return null;
-    }
-    DexEncodedMethod dexEncodedMethod = current.lookupVirtualMethod(method);
-    if (dexEncodedMethod != null) {
-      return dexEncodedMethod;
-    }
-    while (current.superType != factory.objectType) {
-      DexType superType = current.superType;
-      current = appView.definitionFor(superType);
-      if (current == null) {
-        return null;
-      }
-      dexEncodedMethod = current.lookupVirtualMethod(method);
-      if (dexEncodedMethod != null) {
-        return dexEncodedMethod;
-      }
-    }
-    return null;
-  }
-
   private Collection<DexProgramClass> findSynthesizedFrom(Builder<?> builder, DexType holder) {
     for (DexProgramClass synthesizedClass : builder.getSynthesizedClasses()) {
       if (holder == synthesizedClass.getType()) {
@@ -181,6 +176,11 @@
 
   public void synthesizeUtilityClasses(Builder<?> builder, ExecutorService executorService)
       throws ExecutionException {
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      synthesizeEmulatedDispatchMethods(builder);
+    } else {
+      addInterfacesAndForwardingMethods(executorService);
+    }
     if (holders.isEmpty()) {
       return;
     }
@@ -248,6 +248,214 @@
     }
   }
 
+  private void addInterfacesAndForwardingMethods(ExecutorService executorService)
+      throws ExecutionException {
+    assert !appView.options().isDesugaredLibraryCompilation();
+    Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
+    for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
+      map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
+      map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
+    }
+    List<DexEncodedMethod> addedMethods = new ArrayList<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.superType == null) {
+        assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
+        continue;
+      }
+      DexClass dexClass = appView.definitionFor(clazz.superType);
+      // Only performs computation if superclass is a library class, but not object to filter out
+      // the most common case.
+      if (dexClass != null
+          && dexClass.isLibraryClass()
+          && dexClass.type != appView.dexItemFactory().objectType) {
+        for (DexType dexType : map.keySet()) {
+          if (inherit(dexClass.asLibraryClass(), dexType)) {
+            addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
+          }
+        }
+      }
+    }
+    if (addedMethods.isEmpty()) {
+      return;
+    }
+    converter.processMethodsConcurrently(addedMethods, executorService);
+  }
+
+  private boolean inherit(DexLibraryClass clazz, DexType typeToInherit) {
+    DexLibraryClass current = clazz;
+    while (current.type != appView.dexItemFactory().objectType) {
+      if (current.type == typeToInherit) {
+        return true;
+      }
+      current = appView.definitionFor(current.superType).asLibraryClass();
+    }
+    return false;
+  }
+
+  private List<DexEncodedMethod> addInterfacesAndForwardingMethods(
+      DexProgramClass clazz, List<DexMethod> dexMethods) {
+    // BackportedMethodRewriter emulate dispatch: insertion of a marker interface & forwarding
+    // methods.
+    // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
+    // applies up to 24.
+    List<DexEncodedMethod> newForwardingMethods = new ArrayList<>();
+    for (DexMethod dexMethod : dexMethods) {
+      DexType[] newInterfaces = Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
+      newInterfaces[newInterfaces.length - 1] =
+          BackportedMethodRewriter.dispatchInterfaceTypeFor(appView, dexMethod);
+      clazz.interfaces = new DexTypeList(newInterfaces);
+      DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
+      if (dexEncodedMethod == null) {
+        DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
+        clazz.addVirtualMethod(newMethod);
+        newForwardingMethods.add(newMethod);
+      }
+    }
+    return newForwardingMethods;
+  }
+
+  private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
+    // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
+    // even if this results in invalid code, these classes are never desugared.
+    // In desugared library, emulated interface methods can be overridden by retarget lib members.
+    DexMethod forwardMethod = ClassProcessor.retargetMethod(appView, target);
+    // New method will have the same name, proto, and also all the flags of the
+    // default method, including bridge flag.
+    DexMethod newMethod =
+        appView.dexItemFactory().createMethod(clazz.type, target.proto, target.name);
+    DexEncodedMethod dexEncodedMethod = appView.definitionFor(target);
+    MethodAccessFlags newFlags = dexEncodedMethod.accessFlags.copy();
+    newFlags.setSynthetic();
+    ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
+        ForwardMethodSourceCode.builder(newMethod);
+    forwardSourceCodeBuilder
+        .setReceiver(clazz.type)
+        .setTarget(forwardMethod)
+        .setInvokeType(Invoke.Type.STATIC)
+        .setIsInterface(false);
+    return new DexEncodedMethod(
+        newMethod,
+        newFlags,
+        dexEncodedMethod.annotations,
+        dexEncodedMethod.parameterAnnotationsList,
+        new SynthesizedCode(forwardSourceCodeBuilder::build));
+  }
+
+  private void synthesizeEmulatedDispatchMethods(Builder<?> builder) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    if (rewritableMethods.getEmulatedDispatchMethods().isEmpty()) {
+      return;
+    }
+    ClassAccessFlags itfAccessFlags =
+        ClassAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_PUBLIC
+                | Constants.ACC_SYNTHETIC
+                | Constants.ACC_ABSTRACT
+                | Constants.ACC_INTERFACE);
+    ClassAccessFlags holderAccessFlags =
+        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+    for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
+      // Dispatch interface.
+      DexType interfaceType = dispatchInterfaceTypeFor(appView, emulatedDispatchMethod);
+      DexEncodedMethod itfMethod =
+          generateInterfaceDispatchMethod(emulatedDispatchMethod, interfaceType);
+      DexProgramClass dispatchInterface =
+          new DexProgramClass(
+              interfaceType,
+              null,
+              new SynthesizedOrigin("desugared library interface dispatch", getClass()),
+              itfAccessFlags,
+              factory.objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              null,
+              Collections.emptyList(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              new DexEncodedMethod[] {itfMethod},
+              factory.getSkipNameValidationForTesting(),
+              getChecksumSupplier(itfMethod));
+      appView.appInfo().addSynthesizedClass(dispatchInterface);
+      builder.addSynthesizedClass(dispatchInterface, false);
+      // Dispatch holder.
+      DexType holderType = dispatchHolderTypeFor(appView, emulatedDispatchMethod);
+      DexEncodedMethod dispatchMethod =
+          generateHolderDispatchMethod(emulatedDispatchMethod, holderType, itfMethod.method);
+      DexProgramClass dispatchHolder =
+          new DexProgramClass(
+              holderType,
+              null,
+              new SynthesizedOrigin("desugared library dispatch holder class", getClass()),
+              holderAccessFlags,
+              factory.objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              null,
+              Collections.emptyList(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedField.EMPTY_ARRAY,
+              new DexEncodedMethod[] {dispatchMethod},
+              DexEncodedMethod.EMPTY_ARRAY,
+              factory.getSkipNameValidationForTesting(),
+              getChecksumSupplier(dispatchMethod));
+      appView.appInfo().addSynthesizedClass(dispatchHolder);
+      builder.addSynthesizedClass(dispatchHolder, false);
+    }
+  }
+
+  private DexEncodedMethod generateInterfaceDispatchMethod(
+      DexMethod emulatedDispatchMethod, DexType interfaceType) {
+    MethodAccessFlags flags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
+    DexMethod newMethod =
+        factory.createMethod(
+            interfaceType, emulatedDispatchMethod.proto, emulatedDispatchMethod.name);
+    return new DexEncodedMethod(
+        newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null);
+  }
+
+  private DexEncodedMethod generateHolderDispatchMethod(
+      DexMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
+    // The method should look like:
+    // static foo(rcvr, arg0, arg1) {
+    //    if (rcvr instanceof interfaceType) {
+    //      return invoke-interface receiver.foo(arg0, arg1);
+    //    } else {
+    //      return DesugarX.foo(rcvr, arg0, arg1)
+    //    }
+    // We do not deal with complex cases (multiple retargeting of the same signature in the
+    // same inheritance tree, etc., since they do not happen in the most common desugared library.
+    DexItemFactory factory = appView.dexItemFactory();
+    DexProto newProto =
+        factory.prependTypeToProto(emulatedDispatchMethod.holder, emulatedDispatchMethod.proto);
+    DexMethod newMethod =
+        factory.createMethod(dispatchHolder, newProto, emulatedDispatchMethod.name);
+    DexType desugarType =
+        appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getRetargetCoreLibMember()
+            .get(emulatedDispatchMethod.name)
+            .get(emulatedDispatchMethod.holder);
+    DexMethod desugarMethod =
+        factory.createMethod(desugarType, newProto, emulatedDispatchMethod.name);
+    return DexEncodedMethod.toEmulateDispatchLibraryMethod(
+        emulatedDispatchMethod.holder,
+        newMethod,
+        desugarMethod,
+        itfMethod,
+        Collections.emptyList(),
+        appView);
+  }
+
   private ChecksumSupplier getChecksumSupplier(DexEncodedMethod method) {
     if (!appView.options().encodeChecksums) {
       return DexProgramClass::invalidChecksumRequest;
@@ -255,6 +463,31 @@
     return c -> method.method.hashCode();
   }
 
+  public static DexType dispatchInterfaceTypeFor(AppView<?> appView, DexMethod method) {
+    return dispatchTypeFor(appView, method, "dispatchInterface");
+  }
+
+  static DexType dispatchHolderTypeFor(AppView<?> appView, DexMethod method) {
+    return dispatchTypeFor(appView, method, "dispatchHolder");
+  }
+
+  private static DexType dispatchTypeFor(AppView<?> appView, DexMethod method, String suffix) {
+    String desugaredLibPrefix =
+        appView.options().desugaredLibraryConfiguration.getSynthesizedLibraryClassesPackagePrefix();
+    String descriptor =
+        "L"
+            + desugaredLibPrefix
+            + UTILITY_CLASS_NAME_PREFIX
+            + '$'
+            + method.holder.getName()
+            + '$'
+            + method.name
+            + '$'
+            + suffix
+            + ';';
+    return appView.dexItemFactory().createType(descriptor);
+  }
+
   private MethodProvider getMethodProviderOrNull(DexMethod method) {
     DexMethod original = appView.graphLense().getOriginalMethodSignature(method);
     assert original != null;
@@ -285,6 +518,8 @@
     // rewritten while the holder is non final but no superclass implement the method. In this case
     // d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
     private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
+    // non final virtual library methods requiring generation of emulated dispatch.
+    private final Set<DexMethod> emulatedDispatchMethods = Sets.newHashSet();
 
     RewritableMethods(InternalOptions options, AppView<?> appView) {
       DexItemFactory factory = options.itemFactory;
@@ -299,14 +534,18 @@
         initializeAndroidOMethodProviders(factory);
       }
 
+      // The following providers are currently not implemented at any API level in Android.
+      // They however require the Optional/Stream class to be present, either through
+      // desugared libraries or natively. If Optional/Stream class is not present,
+      // we do not desugar to avoid confusion in error messages.
       if (appView.rewritePrefix.hasRewrittenType(factory.optionalType)
           || options.minApiLevel >= AndroidApiLevel.N.getLevel()) {
-        // These are currently not implemented at any API level in Android.
-        // They however require the Optional class to be present, either through
-        // desugared libraries or natively. If Optional class is not present,
-        // we do not desugar to avoid confusion in error messages.
         initializeOptionalMethodProviders(factory);
       }
+      if (appView.rewritePrefix.hasRewrittenType(factory.streamType)
+          || options.minApiLevel >= AndroidApiLevel.N.getLevel()) {
+        initializeStreamMethodProviders(factory);
+      }
 
       // These are currently not implemented at any API level in Android.
       initializeJava9MethodProviders(factory);
@@ -330,6 +569,10 @@
       return false;
     }
 
+    public Set<DexMethod> getEmulatedDispatchMethods() {
+      return emulatedDispatchMethods;
+    }
+
     boolean isEmpty() {
       return rewritable.isEmpty();
     }
@@ -1169,7 +1412,7 @@
 
       // Optional.stream()
       name = factory.createString("stream");
-      proto = factory.createProto(factory.createType("Ljava/util/stream/Stream;"));
+      proto = factory.createProto(factory.streamType);
       method = factory.createMethod(optionalType, proto, name);
       addProvider(
           new StatifyingMethodGenerator(
@@ -1208,6 +1451,19 @@
       }
     }
 
+    private void initializeStreamMethodProviders(DexItemFactory factory) {
+      // Stream
+      DexType streamType = factory.streamType;
+
+      // Stream.ofNullable(object)
+      DexString name = factory.createString("ofNullable");
+      DexProto proto = factory.createProto(factory.streamType, factory.objectType);
+      DexMethod method = factory.createMethod(streamType, proto, name);
+      addProvider(
+          new MethodGenerator(
+              method, BackportedMethods::StreamMethods_ofNullable, "ofNullable") {});
+    }
+
     private void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
       StringDiagnostic warning =
           new StringDiagnostic(
@@ -1232,9 +1488,14 @@
               if (!encodedMethod.isStatic()) {
                 virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
                 virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
-                if (isEmulatedInterfaceDispatch(appView, encodedMethod)) {
+                if (InterfaceMethodRewriter.isEmulatedInterfaceDispatch(appView, encodedMethod)) {
                   // In this case interface method rewriter takes care of it.
                   continue;
+                } else if (!encodedMethod.isFinal()) {
+                  // Virtual rewrites require emulated dispatch for inheritance.
+                  // The call is rewritten to the dispatch holder class instead.
+                  handleEmulateDispatch(appView, encodedMethod.method);
+                  newHolder = dispatchHolderTypeFor(appView, encodedMethod.method);
                 }
               }
               DexProto proto = encodedMethod.method.proto;
@@ -1248,37 +1509,6 @@
       }
     }
 
-    private boolean isEmulatedInterfaceDispatch(AppView<?> appView, DexEncodedMethod method) {
-      // Answers true if this method is already managed through emulated interface dispatch.
-      Map<DexType, DexType> emulateLibraryInterface =
-          appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
-      if (emulateLibraryInterface.isEmpty()) {
-        return false;
-      }
-      DexMethod methodToFind = method.method;
-
-      // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
-      // the method, answers true.
-      LinkedList<DexType> workList = new LinkedList<>();
-      workList.add(methodToFind.holder);
-      while (!workList.isEmpty()) {
-        DexType dexType = workList.removeFirst();
-        DexClass dexClass = appView.definitionFor(dexType);
-        assert dexClass != null; // It is a library class, or we are doing L8 compilation.
-        if (dexClass.isInterface() && emulateLibraryInterface.containsKey(dexType)) {
-          DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(methodToFind);
-          if (dexEncodedMethod != null) {
-            return true;
-          }
-        }
-        Collections.addAll(workList, dexClass.interfaces.values);
-        if (dexClass.superType != appView.dexItemFactory().objectType) {
-          workList.add(dexClass.superType);
-        }
-      }
-      return false;
-    }
-
     private List<DexEncodedMethod> findDexEncodedMethodsWithName(
         DexString methodName, DexClass clazz) {
       List<DexEncodedMethod> found = new ArrayList<>();
@@ -1291,6 +1521,17 @@
       return found;
     }
 
+    private void handleEmulateDispatch(AppView<?> appView, DexMethod method) {
+      emulatedDispatchMethods.add(method);
+      if (!appView.options().isDesugaredLibraryCompilation()) {
+        // Add rewrite rules so keeps rules are correctly generated in the program.
+        DexType dispatchInterfaceType = dispatchInterfaceTypeFor(appView, method);
+        appView.rewritePrefix.rewriteType(dispatchInterfaceType, dispatchInterfaceType);
+        DexType dispatchHolderType = dispatchHolderTypeFor(appView, method);
+        appView.rewritePrefix.rewriteType(dispatchHolderType, dispatchHolderType);
+      }
+    }
+
     private void addProvider(MethodProvider generator) {
       MethodProvider replaced = rewritable.put(generator.method, generator);
       assert replaced == null;
@@ -1457,8 +1698,8 @@
   }
 
   // Specific subclass to transform virtual methods into static desugared methods.
-  // To be correct, the method has to be on a final class, and be implemented directly
-  // on the class (no overrides).
+  // To be correct, the method has to be on a final class or be a final method, and to be
+  // implemented directly on the class (no overrides).
   private static class StatifyingMethodGenerator extends MethodGenerator {
 
     private final DexType receiverType;
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 11c9239..c2f5ae9 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
@@ -4,151 +4,398 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
 import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.DefaultMethodCandidates;
 import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
 
-// Default and static method interface desugaring processor for classes.
-// Adds default interface methods into the class when needed.
+/**
+ * Default and static method interface desugaring processor for classes.
+ *
+ * <p>The core algorithm of the class processing is to ensure that for any type, all of its super
+ * and implements hierarchy is computed first, and based on the summaries of these types the summary
+ * of the class can be computed and the required forwarding methods on that type can be generated.
+ * In other words, the traversal is in top-down (edges from type to its subtypes) topological order.
+ * The traversal is lazy, starting from the unordered set of program classes.
+ */
 final class ClassProcessor {
 
+  // Collection for method signatures that may cause forwarding methods to be created.
+  private static class MethodSignatures {
+    static final MethodSignatures EMPTY = new MethodSignatures(Collections.emptySet());
+
+    static MethodSignatures create(Set<Wrapper<DexMethod>> signatures) {
+      return signatures.isEmpty() ? EMPTY : new MethodSignatures(signatures);
+    }
+
+    final Set<Wrapper<DexMethod>> signatures;
+
+    MethodSignatures(Set<Wrapper<DexMethod>> signatures) {
+      this.signatures = Collections.unmodifiableSet(signatures);
+    }
+
+    MethodSignatures merge(MethodSignatures other) {
+      if (isEmpty()) {
+        return other;
+      }
+      if (other.isEmpty()) {
+        return this;
+      }
+      Set<Wrapper<DexMethod>> merged = new HashSet<>(signatures);
+      merged.addAll(other.signatures);
+      return signatures.size() == merged.size() ? this : new MethodSignatures(merged);
+    }
+
+    MethodSignatures merge(List<MethodSignatures> others) {
+      MethodSignatures merged = this;
+      for (MethodSignatures other : others) {
+        merged = merged.merge(others);
+      }
+      return merged;
+    }
+
+    boolean isEmpty() {
+      return signatures.isEmpty();
+    }
+  }
+
+  // Collection of information known at the point of a given (non-library) class.
+  // This info is immutable and shared as it is often the same on a significant part of the
+  // class hierarchy. Thus, in the case of additions the parent pointer will contain prior info.
+  private static class ClassInfo {
+
+    static final ClassInfo EMPTY = new ClassInfo(null, ImmutableList.of());
+
+    final ClassInfo parent;
+
+    // List of methods that are known to be forwarded to by a forwarding method at this point in the
+    // class hierarchy. This set consists of the default interface methods, i.e., the targets of the
+    // forwarding methods, *not* the forwarding methods themselves.
+    final ImmutableList<DexEncodedMethod> forwardedMethodTargets;
+
+    ClassInfo(ClassInfo parent, ImmutableList<DexEncodedMethod> forwardedMethodTargets) {
+      this.parent = parent;
+      this.forwardedMethodTargets = forwardedMethodTargets;
+    }
+
+    static ClassInfo create(
+        ClassInfo parent, ImmutableList<DexEncodedMethod> forwardedMethodTargets) {
+      return forwardedMethodTargets.isEmpty()
+          ? parent
+          : new ClassInfo(parent, forwardedMethodTargets);
+    }
+
+    public boolean isEmpty() {
+      return this == EMPTY;
+    }
+
+    boolean isTargetedByForwards(DexEncodedMethod method) {
+      return forwardedMethodTargets.contains(method)
+          || (parent != null && parent.isTargetedByForwards(method));
+    }
+  }
+
+  // Helper to keep track of the direct active subclass and nearest program subclass for reporting.
+  private static class ReportingContext {
+    final DexClass directSubClass;
+    final DexProgramClass closestProgramSubClass;
+
+    public ReportingContext(DexClass directSubClass, DexProgramClass closestProgramSubClass) {
+      this.directSubClass = directSubClass;
+      this.closestProgramSubClass = closestProgramSubClass;
+    }
+
+    ReportingContext forClass(DexClass directSubClass) {
+      return new ReportingContext(
+          directSubClass,
+          directSubClass.isProgramClass()
+              ? directSubClass.asProgramClass()
+              : closestProgramSubClass);
+    }
+
+    public void reportDependency(DexClass clazz, AppView<?> appView) {
+      // If the direct subclass is in the compilation unit, report its dependencies.
+      if (clazz != directSubClass && directSubClass.isProgramClass()) {
+        InterfaceMethodRewriter.reportDependencyEdge(
+            clazz, directSubClass.asProgramClass(), appView);
+      }
+    }
+
+    public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
+      rewriter.warnMissingInterface(closestProgramSubClass, closestProgramSubClass, missingType);
+    }
+  }
+
+  // Specialized context to disable reporting when traversing the library strucure.
+  private static class LibraryReportingContext extends ReportingContext {
+    static final LibraryReportingContext LIBRARY_CONTEXT = new LibraryReportingContext();
+
+    LibraryReportingContext() {
+      super(null, null);
+    }
+
+    @Override
+    ReportingContext forClass(DexClass directSubClass) {
+      return this;
+    }
+
+    @Override
+    public void reportDependency(DexClass clazz, AppView<?> appView) {
+      // Don't report dependencies in the library.
+    }
+
+    @Override
+    public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
+      // Ignore missing types in the library.
+    }
+  }
+
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
   private final InterfaceMethodRewriter rewriter;
-  // Set of already processed classes.
-  private final Set<DexClass> processedClasses = Sets.newIdentityHashSet();
-  // Maps already created methods into default methods they were generated based on.
-  private final Map<DexEncodedMethod, DexEncodedMethod> createdMethods = new IdentityHashMap<>();
+  private final Consumer<DexEncodedMethod> newSynthesizedMethodConsumer;
+  private final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
+  private final boolean needsLibraryInfo;
 
-  ClassProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
+  // Mapping from program and classpath classes to their information summary.
+  private final Map<DexClass, ClassInfo> classInfo = new IdentityHashMap<>();
+
+  // Mapping from library classes to their information summary.
+  private final Map<DexLibraryClass, MethodSignatures> libraryClassInfo = new IdentityHashMap<>();
+
+  // Mapping from arbitrary interfaces to an information summary.
+  private final Map<DexClass, MethodSignatures> interfaceInfo = new IdentityHashMap<>();
+
+  // Mapping from actual program classes to the synthesized forwarding methods to be created.
+  private final Map<DexProgramClass, List<DexEncodedMethod>> newSyntheticMethods =
+      new IdentityHashMap<>();
+
+  ClassProcessor(
+      AppView<?> appView,
+      InterfaceMethodRewriter rewriter,
+      Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
     this.rewriter = rewriter;
+    this.newSynthesizedMethodConsumer = newSynthesizedMethodConsumer;
+    needsLibraryInfo =
+        !appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
+            || !appView
+                .options()
+                .desugaredLibraryConfiguration
+                .getRetargetCoreLibMember()
+                .isEmpty();
   }
 
-  final Set<DexEncodedMethod> getForwardMethods() {
-    return createdMethods.keySet();
+  private boolean needsLibraryInfo() {
+    return needsLibraryInfo;
   }
 
-  final void process(DexProgramClass clazz) {
-    assert !clazz.isInterface();
-    if (!processedClasses.add(clazz)) {
-      return; // Has already been processed.
-    }
+  private boolean ignoreLibraryInfo() {
+    return !needsLibraryInfo;
+  }
 
-    // Ensure superclasses are processed first. We need it since we use information
-    // about methods added to superclasses when we decide if we want to add a default
-    // method to class `clazz`.
-    DexType superType = clazz.superType;
-    // If superClass definition is missing, just skip this part and let real processing of its
-    // subclasses report the error if it is required.
-    DexClass superClass = superType == null ? null : appView.definitionFor(superType);
-    if (superClass != null && superType != dexItemFactory.objectType) {
-      if (superClass.isInterface()) {
-        throw new CompilationError("Interface `" + superClass.toSourceString()
-            + "` used as super class of `" + clazz.toSourceString() + "`.");
-      }
-      // We assume that library classes don't need to be processed, since they
-      // are provided by a runtime not supporting default interface methods.  We
-      // also skip classpath classes, which results in sub-optimal behavior in
-      // case classpath superclass when processed adds a default method which
-      // could have been reused in this class otherwise.
-      if (superClass.isProgramClass()) {
-        process(superClass.asProgramClass());
+  public void processClass(DexProgramClass clazz) {
+    visitClassInfo(clazz, new ReportingContext(clazz, clazz));
+  }
+
+  final void addSyntheticMethods() {
+    for (DexProgramClass clazz : newSyntheticMethods.keySet()) {
+      List<DexEncodedMethod> newForwardingMethods = newSyntheticMethods.get(clazz);
+      if (newForwardingMethods != null) {
+        clazz.appendVirtualMethods(newForwardingMethods);
+        newForwardingMethods.forEach(newSynthesizedMethodConsumer);
       }
     }
+  }
 
-    // When inheriting from a library class, the library class may implement interfaces to
-    // desugar. We therefore need to look at the interfaces of the library classes.
-    boolean desugaredLibraryLookup =
-        superClass != null
-            && superClass.isLibraryClass()
-            && appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().size()
-                > 0;
+  // Computes the set of method signatures that may need forwarding methods on derived classes.
+  private MethodSignatures computeInterfaceInfo(DexClass iface, MethodSignatures signatures) {
+    assert iface.isInterface();
+    assert iface.superType == dexItemFactory.objectType;
+    // Add non-library default methods as well as those for desugared library classes.
+    if (!iface.isLibraryClass() || (needsLibraryInfo() && rewriter.isInDesugaredLibrary(iface))) {
+      List<DexEncodedMethod> methods = iface.virtualMethods();
+      List<Wrapper<DexMethod>> additions = new ArrayList<>(methods.size());
+      for (DexEncodedMethod method : methods) {
+        if (method.isDefaultMethod()) {
+          additions.add(equivalence.wrap(method.method));
+        }
+      }
+      if (!additions.isEmpty()) {
+        signatures = signatures.merge(MethodSignatures.create(new HashSet<>(additions)));
+      }
+    }
+    return signatures;
+  }
 
-    if (clazz.interfaces.isEmpty() && !desugaredLibraryLookup) {
-      // Since superclass has already been processed and it has all missing methods
-      // added, these methods will be inherited by `clazz`, and only need to be revised
-      // in case this class has *additional* interfaces implemented, which may change
-      // the entire picture of the default method selection in runtime.
+  // Computes the set of signatures of that may need forwarding methods on classes that derive
+  // from a library class.
+  private MethodSignatures computeLibraryClassInfo(
+      DexLibraryClass clazz, MethodSignatures signatures) {
+    // The result is the identity as the library class does not itself contribute to the set.
+    return signatures;
+  }
+
+  // The computation of a class information and the insertions of forwarding methods.
+  private ClassInfo computeClassInfo(
+      DexClass clazz, ClassInfo superInfo, MethodSignatures signatures) {
+    Builder<DexEncodedMethod> additionalForwards = ImmutableList.builder();
+    for (Wrapper<DexMethod> wrapper : signatures.signatures) {
+      resolveForwardForSignature(
+          clazz,
+          wrapper.get(),
+          (targetHolder, target) -> {
+            if (!superInfo.isTargetedByForwards(target)) {
+              additionalForwards.add(target);
+              addForwardingMethod(targetHolder, target, clazz);
+            }
+          });
+    }
+    return ClassInfo.create(superInfo, additionalForwards.build());
+  }
+
+  // Resolves a method signature from the point of 'clazz', if it must target a default method
+  // the 'addForward' call-back is called with the target of the forward.
+  private void resolveForwardForSignature(
+      DexClass clazz, DexMethod method, BiConsumer<DexClass, DexEncodedMethod> addForward) {
+    ResolutionResult resolution = appView.appInfo().resolveMethod(clazz, method);
+    // If resolution fails, install a method throwing IncompatibleClassChangeError.
+    if (resolution.isFailedResolution()) {
+      assert resolution instanceof IncompatibleClassResult;
+      addICCEThrowingMethod(method, clazz);
+      return;
+    }
+    DexEncodedMethod target = resolution.getSingleTarget();
+    DexClass targetHolder = appView.definitionFor(target.method.holder);
+    // Don-t forward if the target is explicitly marked as 'dont-rewrite'
+    if (targetHolder == null || dontRewrite(targetHolder, target)) {
       return;
     }
 
-    // Collect the default interface methods to be added to this class.
-    DefaultMethodCandidates methodsToImplement =
-        collectMethodsToImplement(clazz, desugaredLibraryLookup);
-    if (methodsToImplement.isEmpty()) {
+    // If resolution targets a default interface method, forward it.
+    if (targetHolder.isInterface() && target.isDefaultMethod()) {
+      addForward.accept(targetHolder, target);
       return;
     }
 
-    // Add the methods.
-    List<DexEncodedMethod> newForwardingMethods = new ArrayList<>(methodsToImplement.size());
-    for (DexEncodedMethod method : methodsToImplement.candidates) {
-      assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
-      DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
-      newForwardingMethods.add(newMethod);
-      createdMethods.put(newMethod, method);
+    // Remaining edge cases only pertain to desugaring of library methods.
+    DexLibraryClass libraryHolder = targetHolder.asLibraryClass();
+    if (libraryHolder == null || ignoreLibraryInfo()) {
+      return;
     }
-    for (DexEncodedMethod conflict : methodsToImplement.conflicts.keySet()) {
-      assert conflict.accessFlags.isPublic() && !conflict.accessFlags.isAbstract();
-      DexEncodedMethod newMethod = addICCEThrowingMethod(conflict, clazz);
-      newForwardingMethods.add(newMethod);
-      createdMethods.put(newMethod, conflict);
+
+    if (isRetargetMethod(libraryHolder, target)) {
+      addForward.accept(targetHolder, target);
+      return;
     }
-    clazz.appendVirtualMethods(newForwardingMethods);
+
+    // If target is a non-interface library class it may be an emulated interface.
+    if (!libraryHolder.isInterface()) {
+      // Here we use step-3 of resolution to find a maximally specific default interface method.
+      target =
+          appView
+              .appInfo()
+              .resolveMaximallySpecificMethods(libraryHolder, method)
+              .getSingleTarget();
+      if (target != null && rewriter.isEmulatedInterface(target.method.holder)) {
+        targetHolder = appView.definitionFor(target.method.holder);
+        addForward.accept(targetHolder, target);
+      }
+    }
   }
 
-  private DexEncodedMethod addICCEThrowingMethod(DexEncodedMethod method, DexClass clazz) {
-    DexMethod newMethod =
-        dexItemFactory.createMethod(clazz.type, method.method.proto, method.method.name);
-    return new DexEncodedMethod(
-        newMethod,
-        method.accessFlags.copy(),
-        DexAnnotationSet.empty(),
-        ParameterAnnotationsList.empty(),
-        new SynthesizedCode(
-            callerPosition ->
-                new ExceptionThrowingSourceCode(
-                    clazz.type, method.method, callerPosition, dexItemFactory.icceType)));
+  private boolean isRetargetMethod(DexLibraryClass holder, DexEncodedMethod method) {
+    assert needsLibraryInfo();
+    assert holder.type == method.method.holder;
+    assert method.isVirtualMethod();
+    if (method.isFinal()) {
+      return false;
+    }
+    Map<DexType, DexType> typeMap =
+        appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getRetargetCoreLibMember()
+            .get(method.method.name);
+    return typeMap != null && typeMap.containsKey(holder.type);
   }
 
-  private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
-    DexMethod method = defaultMethod.method;
-    DexClass target = appView.definitionFor(method.holder);
+  private boolean dontRewrite(DexClass clazz, DexEncodedMethod method) {
+    return needsLibraryInfo() && clazz.isLibraryClass() && rewriter.dontRewrite(method.method);
+  }
+
+  // Construction of actual forwarding methods.
+
+  private void addSyntheticMethod(DexProgramClass clazz, DexEncodedMethod newMethod) {
+    newSyntheticMethods.computeIfAbsent(clazz, key -> new ArrayList<>()).add(newMethod);
+  }
+
+  private void addICCEThrowingMethod(DexMethod method, DexClass clazz) {
+    if (!clazz.isProgramClass()) {
+      return;
+    }
+    DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
+    DexEncodedMethod newEncodedMethod =
+        new DexEncodedMethod(
+            newMethod,
+            MethodAccessFlags.fromCfAccessFlags(Opcodes.ACC_PUBLIC, false),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            new SynthesizedCode(
+                callerPosition ->
+                    new ExceptionThrowingSourceCode(
+                        clazz.type, method, callerPosition, dexItemFactory.icceType)));
+    addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
+  }
+
+  // Note: The parameter 'target' may be a public method on a class in case of desugared
+  // library retargeting (See below target.isInterface check).
+  private void addForwardingMethod(DexClass targetHolder, DexEncodedMethod target, DexClass clazz) {
+    assert targetHolder != null;
+    if (!clazz.isProgramClass()) {
+      return;
+    }
+    DexMethod method = target.method;
     // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
     // even if this results in invalid code, these classes are never desugared.
-    assert target != null;
     // In desugared library, emulated interface methods can be overridden by retarget lib members.
     DexMethod forwardMethod =
-        target.isInterface()
+        targetHolder.isInterface()
             ? rewriter.defaultAsMethodOfCompanionClass(method)
-            : retargetMethod(method);
+            : retargetMethod(appView, method);
     // New method will have the same name, proto, and also all the flags of the
     // default method, including bridge flag.
     DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
-    MethodAccessFlags newFlags = defaultMethod.accessFlags.copy();
+    MethodAccessFlags newFlags = target.accessFlags.copy();
     // Some debuggers (like IntelliJ) automatically skip synthetic methods on single step.
     newFlags.setSynthetic();
     ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
@@ -158,185 +405,116 @@
         .setTarget(forwardMethod)
         .setInvokeType(Invoke.Type.STATIC)
         .setIsInterface(false); // Holder is companion class, not an interface.
-    return new DexEncodedMethod(
-        newMethod,
-        newFlags,
-        defaultMethod.annotations,
-        defaultMethod.parameterAnnotationsList,
-        new SynthesizedCode(forwardSourceCodeBuilder::build));
+    DexEncodedMethod newEncodedMethod =
+        new DexEncodedMethod(
+            newMethod,
+            newFlags,
+            target.annotations,
+            target.parameterAnnotationsList,
+            new SynthesizedCode(forwardSourceCodeBuilder::build));
+    addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
   }
 
-  private DexMethod retargetMethod(DexMethod method) {
+  static DexMethod retargetMethod(AppView<?> appView, DexMethod method) {
     Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
         appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
     Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.name);
     assert typeMap != null;
     assert typeMap.get(method.holder) != null;
-    return dexItemFactory.createMethod(
-        typeMap.get(method.holder),
-        dexItemFactory.prependTypeToProto(method.holder, method.proto),
-        method.name);
+    return appView
+        .dexItemFactory()
+        .createMethod(
+            typeMap.get(method.holder),
+            appView.dexItemFactory().prependTypeToProto(method.holder, method.proto),
+            method.name);
   }
 
-  // For a given class `clazz` inspects all interfaces it implements directly or
-  // indirectly and collect a set of all default methods to be implemented
-  // in this class.
-  private DefaultMethodCandidates collectMethodsToImplement(
-      DexClass clazz, boolean desugaredLibraryLookup) {
-    DefaultMethodsHelper helper = new DefaultMethodsHelper();
-    DexClass current = clazz;
-    List<DexEncodedMethod> accumulatedVirtualMethods = new ArrayList<>();
-    // Collect candidate default methods by inspecting interfaces implemented
-    // by this class as well as its superclasses.
-    //
-    // We assume here that interfaces implemented by java.lang.Object don't
-    // have default methods to desugar since they are library interfaces. And we assume object
-    // methods don't hide any default interface methods. Default interface method matching Object's
-    // methods is supposed to fail with a compilation error.
-    // Note that this last assumption will be broken if Object API is augmented with a new method in
-    // the future.
-    while (current.type != dexItemFactory.objectType) {
-      for (DexType type : current.interfaces.values) {
-        helper.merge(rewriter.getOrCreateInterfaceInfo(clazz, current, type));
-      }
+  // Topological order traversal and its helpers.
 
-      // TODO(anyone): Using clazz here instead of current looks suspicious, should this be hoisted
-      // out of the loop or changed to current?
-      accumulatedVirtualMethods.addAll(clazz.virtualMethods());
-
-      List<DexEncodedMethod> defaultMethodsInDirectInterface = helper.createFullList();
-
-      List<DexEncodedMethod> toBeImplementedFromDirectInterface =
-          new ArrayList<>(defaultMethodsInDirectInterface.size());
-      hideCandidates(accumulatedVirtualMethods,
-          defaultMethodsInDirectInterface,
-          toBeImplementedFromDirectInterface);
-      // toBeImplementedFromDirectInterface are those that we know for sure we need to implement by
-      // looking at the already desugared super classes.
-      // Remaining methods in defaultMethodsInDirectInterface are those methods we need to look at
-      // the hierarchy to know how they should be handled.
-      if (toBeImplementedFromDirectInterface.isEmpty()
-          && defaultMethodsInDirectInterface.isEmpty()
-          && !desugaredLibraryLookup) {
-        // No interface with default in direct hierarchy, nothing to do: super already has all that
-        // is needed.
-        return DefaultMethodCandidates.empty();
-      }
-
-      if (current.superType == null) {
-        // TODO(anyone): Can this ever happen? It seems the loop stops on Object.
-        break;
-      } else {
-        DexClass superClass = appView.definitionFor(current.superType);
-        if (superClass != null) {
-          // TODO(b/138988172): Can we avoid traversing the full hierarchy for each type?
-          InterfaceMethodRewriter.reportDependencyEdge(superClass, current, appView);
-          current = superClass;
-        } else {
-          String message = "Default method desugaring of `" + clazz.toSourceString() + "` failed";
-          if (current == clazz) {
-            message += " because its super class `" +
-                clazz.superType.toSourceString() + "` is missing";
-          } else {
-            message +=
-                " because it's hierarchy is incomplete. The class `"
-                    + current.superType.toSourceString()
-                    + "` is missing and it is the declared super class of `"
-                    + current.toSourceString() + "`";
-          }
-          throw new CompilationError(message);
-        }
-      }
+  private DexClass definitionOrNull(DexType type, ReportingContext context) {
+    // No forwards at the top of the class hierarchy (assuming java.lang.Object is never amended).
+    if (type == null || type == dexItemFactory.objectType) {
+      return null;
     }
-
-    DefaultMethodCandidates candidateSet = helper.createCandidatesList();
-    if (candidateSet.isEmpty()) {
-      return candidateSet;
+    DexClass clazz = appView.definitionFor(type);
+    if (clazz == null) {
+      context.reportMissingType(type, rewriter);
+      return null;
     }
-
-    // Remove from candidates methods defined in class or any of its superclasses.
-    List<DexEncodedMethod> candidates = candidateSet.candidates;
-    List<DexEncodedMethod> toBeImplemented = new ArrayList<>(candidates.size());
-    current = clazz;
-    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
-    while (true) {
-      // In desugared library look-up, methods from library classes cannot hide methods from
-      // emulated interfaces (the method being desugared implied the implementation is not
-      // present in the library class), except through retarget core lib member.
-      if (desugaredLibraryLookup && current.isLibraryClass()) {
-        Iterator<DexEncodedMethod> iterator = candidates.iterator();
-        while (iterator.hasNext()) {
-          DexEncodedMethod candidate = iterator.next();
-          if (rewriter.isEmulatedInterface(candidate.method.holder)
-              && current.lookupVirtualMethod(candidate.method) != null) {
-            // A library class overrides an emulated interface method. This override is valid
-            // only if it goes through retarget core lib member, else it needs to be implemented.
-            Map<DexType, DexType> typeMap = retargetCoreLibMember.get(candidate.method.name);
-            if (typeMap != null && typeMap.containsKey(current.type)) {
-              // A rewrite needs to be performed, but instead of rewriting to the companion class,
-              // D8/R8 needs to rewrite to the retarget member.
-              toBeImplemented.add(current.lookupVirtualMethod(candidate.method));
-            } else {
-              toBeImplemented.add(candidate);
-              iterator.remove();
-            }
-          }
-        }
-      }
-      // Hide candidates by virtual method of the class.
-      hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
-      if (candidates.isEmpty()) {
-        return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
-      }
-
-      DexType superType = current.superType;
-      DexClass superClass = null;
-      if (superType != null) {
-        superClass = appView.definitionFor(superType);
-        // It's available or we would have failed while analyzing the hierarchy for interfaces.
-        assert superClass != null;
-      }
-      if (superClass == null || superType == dexItemFactory.objectType) {
-        // Note that default interface methods must never have same
-        // name/signature as any method in java.lang.Object (JLS §9.4.1.2).
-
-        // Everything still in candidate list is not hidden.
-        toBeImplemented.addAll(candidates);
-
-        return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
-      }
-      current = superClass;
-    }
+    return clazz;
   }
 
-  private void hideCandidates(
-      List<DexEncodedMethod> virtualMethods,
-      Collection<DexEncodedMethod> candidates,
-      List<DexEncodedMethod> toBeImplemented) {
-    Iterator<DexEncodedMethod> it = candidates.iterator();
-    while (it.hasNext()) {
-      DexEncodedMethod candidate = it.next();
-      for (DexEncodedMethod encoded : virtualMethods) {
-        if (candidate.method.match(encoded)) {
-          // Found a methods hiding the candidate.
-          DexEncodedMethod basedOnCandidate = createdMethods.get(encoded);
-          if (basedOnCandidate != null) {
-            // The method we found is a method we have generated for a default interface
-            // method in a superclass. If the method is based on the same candidate we don't
-            // need to re-generate this method again since it is going to be inherited.
-            if (basedOnCandidate != candidate) {
-              // Need to re-generate since the inherited version is
-              // based on a different candidate.
-              toBeImplemented.add(candidate);
-            }
-          }
+  private ClassInfo visitClassInfo(DexType type, ReportingContext context) {
+    DexClass clazz = definitionOrNull(type, context);
+    return clazz == null ? ClassInfo.EMPTY : visitClassInfo(clazz, context);
+  }
 
-          // Done with this candidate.
-          it.remove();
-          break;
-        }
-      }
+  private ClassInfo visitClassInfo(DexClass clazz, ReportingContext context) {
+    assert !clazz.isInterface();
+    if (clazz.isLibraryClass()) {
+      return ClassInfo.EMPTY;
     }
+    context.reportDependency(clazz, appView);
+    return classInfo.computeIfAbsent(clazz, key -> visitClassInfoRaw(key, context));
+  }
+
+  private ClassInfo visitClassInfoRaw(DexClass clazz, ReportingContext context) {
+    // We compute both library and class information, but one of them is empty, since a class is
+    // a library class or is not, but cannot be both.
+    ReportingContext thisContext = context.forClass(clazz);
+    ClassInfo superInfo = visitClassInfo(clazz.superType, thisContext);
+    MethodSignatures signatures = visitLibraryClassInfo(clazz.superType);
+    assert superInfo.isEmpty() || signatures.isEmpty();
+    for (DexType iface : clazz.interfaces.values) {
+      signatures = signatures.merge(visitInterfaceInfo(iface, thisContext));
+    }
+    return computeClassInfo(clazz, superInfo, signatures);
+  }
+
+  private MethodSignatures visitLibraryClassInfo(DexType type) {
+    // No desugaring required, no library class analysis.
+    if (ignoreLibraryInfo()) {
+      return MethodSignatures.EMPTY;
+    }
+    DexClass clazz = definitionOrNull(type, LibraryReportingContext.LIBRARY_CONTEXT);
+    return clazz == null ? MethodSignatures.EMPTY : visitLibraryClassInfo(clazz);
+  }
+
+  private MethodSignatures visitLibraryClassInfo(DexClass clazz) {
+    assert !clazz.isInterface();
+    return clazz.isLibraryClass()
+        ? libraryClassInfo.computeIfAbsent(clazz.asLibraryClass(), this::visitLibraryClassInfoRaw)
+        : MethodSignatures.EMPTY;
+  }
+
+  private MethodSignatures visitLibraryClassInfoRaw(DexLibraryClass clazz) {
+    MethodSignatures signatures = visitLibraryClassInfo(clazz.superType);
+    for (DexType iface : clazz.interfaces.values) {
+      signatures =
+          signatures.merge(visitInterfaceInfo(iface, LibraryReportingContext.LIBRARY_CONTEXT));
+    }
+    return computeLibraryClassInfo(clazz, signatures);
+  }
+
+  private MethodSignatures visitInterfaceInfo(DexType iface, ReportingContext context) {
+    DexClass definition = definitionOrNull(iface, context);
+    return definition == null ? MethodSignatures.EMPTY : visitInterfaceInfo(definition, context);
+  }
+
+  private MethodSignatures visitInterfaceInfo(DexClass iface, ReportingContext context) {
+    if (iface.isLibraryClass() && ignoreLibraryInfo()) {
+      return MethodSignatures.EMPTY;
+    }
+    context.reportDependency(iface, appView);
+    return interfaceInfo.computeIfAbsent(iface, key -> visitInterfaceInfoRaw(key, context));
+  }
+
+  private MethodSignatures visitInterfaceInfoRaw(DexClass iface, ReportingContext context) {
+    ReportingContext thisContext = context.forClass(iface);
+    MethodSignatures signatures = MethodSignatures.EMPTY;
+    for (DexType superiface : iface.interfaces.values) {
+      signatures = signatures.merge(visitInterfaceInfo(superiface, thisContext));
+    }
+    return computeInterfaceInfo(iface, signatures);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
index 2484113..07277bc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
@@ -6,66 +6,15 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
 import java.util.Set;
 
 // Helper class implementing bunch of default interface method handling operations.
 final class DefaultMethodsHelper {
-
-  // Collection of default methods that need to have generated forwarding methods.
-  public static class DefaultMethodCandidates {
-    final List<DexEncodedMethod> candidates;
-    final Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts;
-
-    private static final DefaultMethodCandidates EMPTY =
-        new DefaultMethodCandidates(Collections.emptyList(), Collections.emptyMap());
-
-    public static DefaultMethodCandidates empty() {
-      return EMPTY;
-    }
-
-    public DefaultMethodCandidates(
-        List<DexEncodedMethod> candidates,
-        Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts) {
-      this.candidates = candidates;
-      this.conflicts = conflicts;
-    }
-
-    public int size() {
-      return candidates.size() + conflicts.size();
-    }
-
-    public boolean isEmpty() {
-      return candidates.isEmpty() && conflicts.isEmpty();
-    }
-  }
-
-  // Equivalence wrapper for comparing two method signatures modulo holder type.
-  private static class SignatureEquivalence extends Equivalence<DexEncodedMethod> {
-
-    @Override
-    protected boolean doEquivalent(DexEncodedMethod method1, DexEncodedMethod method2) {
-      return method1.method.match(method2.method);
-    }
-
-    @Override
-    protected int doHash(DexEncodedMethod method) {
-      return Objects.hash(method.method.name, method.method.proto);
-    }
-  }
-
   // Current set of default interface methods, may overlap with `hidden`.
   private final Set<DexEncodedMethod> candidates = Sets.newIdentityHashSet();
   // Current set of known hidden default interface methods.
@@ -127,63 +76,6 @@
     candidates.add(encoded);
   }
 
-  final DefaultMethodCandidates createCandidatesList() {
-    // The common cases is for no default methods or a single one.
-    if (candidates.isEmpty()) {
-      return DefaultMethodCandidates.empty();
-    }
-    if (candidates.size() == 1 && hidden.isEmpty()) {
-      return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
-    }
-    // In case there are more we need to check for potential duplicates and treat them specially
-    // to preserve the IncompatibleClassChangeError that would arise at runtime.
-    int maxSize = candidates.size();
-    SignatureEquivalence equivalence = new SignatureEquivalence();
-    Map<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> groups = new HashMap<>(maxSize);
-    boolean foundConflicts = false;
-    for (DexEncodedMethod candidate : candidates) {
-      if (hidden.contains(candidate)) {
-        continue;
-      }
-      Wrapper<DexEncodedMethod> key = equivalence.wrap(candidate);
-      List<DexEncodedMethod> conflicts = groups.get(key);
-      if (conflicts != null) {
-        foundConflicts = true;
-      } else {
-        conflicts = new ArrayList<>(maxSize);
-        groups.put(key, conflicts);
-      }
-      conflicts.add(candidate);
-    }
-    // In the fast path we don't expect any conflicts or hidden candidates.
-    if (!foundConflicts && hidden.isEmpty()) {
-      return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
-    }
-    // Slow case in the case of conflicts or hidden candidates build the result.
-    List<DexEncodedMethod> actualCandidates = new ArrayList<>(groups.size());
-    Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts = new IdentityHashMap<>();
-    for (Entry<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> entry : groups.entrySet()) {
-      if (entry.getValue().size() == 1) {
-        actualCandidates.add(entry.getKey().get());
-      } else {
-        conflicts.put(entry.getKey().get(), entry.getValue());
-      }
-    }
-    return new DefaultMethodCandidates(actualCandidates, conflicts);
-  }
-
-  final List<DexEncodedMethod> createFullList() {
-    if (candidates.isEmpty() && hidden.isEmpty()) {
-      return Collections.emptyList();
-    }
-
-    List<DexEncodedMethod> fullList =
-        new ArrayList<DexEncodedMethod>(candidates.size() + hidden.size());
-    fullList.addAll(candidates);
-    fullList.addAll(hidden);
-    return fullList;
-  }
-
   // Create default interface collection based on collected information.
   final Collection wrapInCollection() {
     candidates.removeAll(hidden);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 1c11d78..fd954e5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -114,6 +114,10 @@
         : "";
   }
 
+  public String getSynthesizedLibraryClassesPackagePrefix() {
+    return synthesizedLibraryClassesPackagePrefix;
+  }
+
   public Map<String, String> getRewritePrefix() {
     return rewritePrefix;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index d75b84c..c9bd364 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -381,7 +381,7 @@
     appView
         .options()
         .reporter
-        .info(
+        .warning(
             new StringDiagnostic(
                 "Desugared library API conversion: cannot wrap final methods "
                     + Arrays.toString(methodArray)
@@ -440,17 +440,6 @@
         workList.add(superClass);
       }
     }
-    // 10 is large enough to avoid warnings on Clock/Function, but not on Stream.
-    if (implementedMethods.size() > 10) {
-      appView
-          .options()
-          .reporter
-          .info(
-              new StringDiagnostic(
-                  "Desugared library API conversion: Generating a large wrapper for "
-                      + libraryClass.type
-                      + ". Is that the intended behavior?"));
-    }
     return implementedMethods;
   }
 
@@ -585,7 +574,7 @@
                   factory.createString(
                       "Unsupported conversion for "
                           + type
-                          + ". See compilation time infos for more details."),
+                          + ". See compilation time warnings for more details."),
                   holder)
               .generateCfCode();
     } else {
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 eb855d1..679f927 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
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.GraphLense;
 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;
@@ -60,6 +61,7 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
 //
 // Default and static interface method desugaring rewriter (note that lambda
@@ -103,8 +105,6 @@
   private final Map<DexType, DexType> emulatedInterfaces = new IdentityHashMap<>();
   // The emulatedMethod set is there to avoid doing the emulated look-up too often.
   private final Set<DexString> emulatedMethods = Sets.newIdentityHashSet();
-  private ConcurrentHashMap<DexMethod, DexType> nearestEmulatedInterfaceCache =
-      new ConcurrentHashMap<>();
 
   // All forwarding methods generated during desugaring. We don't synchronize access
   // to this collection since it is only filled in ClassProcessor running synchronously.
@@ -272,7 +272,7 @@
                 new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
           } else {
-            DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+            DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
             if (dexType != null) {
               // That invoke super may not resolve since the super method may not be present
               // since it's in the emulated interface. We need to force resolution. If it resolves
@@ -373,7 +373,7 @@
         if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
           InvokeMethod invokeMethod = instruction.asInvokeMethod();
           DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-          DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+          DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
           if (dexType != null) {
             rewriteCurrentInstructionToEmulatedInterfaceCall(
                 dexType, invokedMethod, invokeMethod, instructions);
@@ -383,7 +383,7 @@
     }
   }
 
-  private DexType nearestEmulatedInterfaceOrNull(DexMethod invokedMethod) {
+  private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
     // Here we try to avoid doing the expensive look-up on all invokes.
     if (!emulatedMethods.contains(invokedMethod.name)) {
       return null;
@@ -393,20 +393,30 @@
     if (dexClass == null) {
       return null;
     }
-    // TODO(b/120884788): Make sure program class are looked up before library class for
-    // CoreLib compilation or look again into all desugared library emulation.
-    // Outside of core libraries, only library classes are rewritten. In core libraries,
-    // some classes are present both as program and library class, and definitionFor
-    // answers the program class so this is not true.
+    // TODO(b/120884788): Make sure program class are looked up before library class.
+    // Since program classes are desugared, no need to rewrite invokes which can target only
+    // program types.
     if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
       return null;
     }
-    // We always rewrite interfaces, but classes are rewritten only if they are not already
-    // desugared (CoreLibrary classes efficient implementation).
-    if (!dexClass.isInterface() && isInDesugaredLibrary(dexClass)) {
+    // Since desugared library classes are desugared, no need to rewrite invokes which can target
+    // only such classes program types.
+    if (appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
       return null;
     }
-    return nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
+    ResolutionResult resolutionResult =
+        appView.appInfo().resolveMaximallySpecificMethods(dexClass, invokedMethod);
+    if (!resolutionResult.isSingleResolution()) {
+      // 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;
+    }
+    return null;
   }
 
   private void rewriteCurrentInstructionToEmulatedInterfaceCall(
@@ -425,137 +435,11 @@
     }
   }
 
-  private DexType nearestEmulatedInterfaceImplementingWithCache(DexMethod method) {
-    DexType sentinel = DexItemFactory.nullValueType;
-    if (nearestEmulatedInterfaceCache.containsKey(method)) {
-      DexType dexType = nearestEmulatedInterfaceCache.get(method);
-      if (dexType == sentinel) {
-        return null;
-      }
-      return dexType;
-    } else {
-      DexType value = nearestEmulatedInterfaceImplementing(method);
-      DexType putValue = value == null ? sentinel : value;
-      nearestEmulatedInterfaceCache.put(method, putValue);
-      return value;
-    }
-  }
-
-  private DexType nearestEmulatedInterfaceImplementing(DexMethod method) {
-    // Find the nearest emulated interface implementing method in a non abstract way.
-    // Answers null if none.
-    if (!method.holder.isClassType()) {
-      return null;
-    }
-    // 1. Direct match against the interface for invokeInterface.
-    if (isMatchingEmulatedInterface(method.holder, method)) {
-      return method.holder;
-    }
-    List<DexType> foundInterfaces = new ArrayList<>();
-    Set<DexType> foundEmulatedInterfaces = Sets.newIdentityHashSet();
-    // 2. Walk superclass hierarchy to find implemented interfaces, pick the minimal of them.
-    DexType current = method.holder;
-    DexClass currentClass = appView.definitionFor(current);
-    while (currentClass != null) {
-      for (DexType itf : currentClass.interfaces.values) {
-        if (isMatchingEmulatedInterface(itf, method)) {
-          foundEmulatedInterfaces.add(itf);
-        } else if (foundEmulatedInterfaces.isEmpty()) {
-          foundInterfaces.add(itf);
-        }
-      }
-      current = currentClass.superType;
-      currentClass = current == null ? null : appView.definitionFor(current);
-    }
-    if (!foundEmulatedInterfaces.isEmpty()) {
-      return minimalInterfaceOf(foundEmulatedInterfaces);
-    }
-    // 3. Walk the interfaces hierachies to find implemented interfaces, pick the minimal of them.
-    LinkedList<DexType> workList = new LinkedList<>(foundInterfaces);
-    while (!workList.isEmpty()) {
-      currentClass = appView.definitionFor(workList.removeFirst());
-      if (currentClass != null) {
-        for (DexType itf : currentClass.interfaces.values) {
-          if (isMatchingEmulatedInterface(itf, method)) {
-            foundEmulatedInterfaces.add(itf);
-          } else if (!foundInterfaces.contains(itf)) {
-            foundInterfaces.add(itf);
-            workList.add(itf);
-          }
-        }
-      }
-    }
-    if (!foundEmulatedInterfaces.isEmpty()) {
-      return minimalInterfaceOf(foundEmulatedInterfaces);
-    }
-    return null;
-  }
-
-  private boolean isMatchingEmulatedInterface(DexType itf, DexMethod method) {
-    DexClass dexClass = appView.definitionFor(itf);
-    DexEncodedMethod encodedMethod = dexClass == null ? null : dexClass.lookupMethod(method);
-    return emulatedInterfaces.containsKey(itf)
-        && encodedMethod != null
-        && !encodedMethod.isAbstract();
-  }
-
-  private DexType minimalInterfaceOf(Set<DexType> interfaces) {
-    assert interfaces.size() > 0;
-    if (interfaces.size() == 1) {
-      return interfaces.iterator().next();
-    }
-    // We may have two classes which appears unrelated due to a missing interface in the list,
-    // i.e., A implements B implements C, but B is not implementing the method.
-    // We look up interface hierarchy here for all interfaces to determine interfaces with children.
-    // The unique interface without children is returned (nearest interface).
-    final ArrayList<DexType> hasChildren = new ArrayList<>();
-    for (DexType anInterface : interfaces) {
-      LinkedList<DexType> workList = new LinkedList<>();
-      workList.add(anInterface);
-      while (!workList.isEmpty()) {
-        DexType itf = workList.removeFirst();
-        DexClass itfClass = appView.definitionFor(itf);
-        if (itfClass == null) {
-          continue;
-        }
-        for (DexType superItf : itfClass.interfaces.values) {
-          if (interfaces.contains(superItf)) {
-            hasChildren.add(superItf);
-          } else {
-            workList.add(superItf);
-          }
-        }
-      }
-    }
-    DexType result = null;
-    for (DexType anInterface : interfaces) {
-      if (!hasChildren.contains(anInterface)) {
-        if (result != null) {
-          throw new CompilationError(
-              "Multiple emulated interfaces, non related to each other, "
-                  + "implementing the same default method ("
-                  + anInterface
-                  + ","
-                  + result
-                  + ")");
-        }
-        result = anInterface;
-      }
-    }
-    if (result == null) {
-      throw new CompilationError(
-          "All emulated interfaces "
-              + Arrays.toString(interfaces.toArray())
-              + " inherit from each other.");
-    }
-    return result;
-  }
-
-  boolean isNonDesugaredLibraryClass(DexClass clazz) {
+  private boolean isNonDesugaredLibraryClass(DexClass clazz) {
     return clazz.isLibraryClass() && !isInDesugaredLibrary(clazz);
   }
 
-  private boolean isInDesugaredLibrary(DexClass clazz) {
+  boolean isInDesugaredLibrary(DexClass clazz) {
     assert clazz.isLibraryClass() || options.isDesugaredLibraryCompilation();
     if (emulatedInterfaces.containsKey(clazz.type)) {
       return true;
@@ -563,7 +447,7 @@
     return appView.rewritePrefix.hasRewrittenType(clazz.type);
   }
 
-  private boolean dontRewrite(DexMethod method) {
+  boolean dontRewrite(DexMethod method) {
     for (Pair<DexType, DexString> dontRewrite :
         options.desugaredLibraryConfiguration.getDontRewriteInvocation()) {
       if (method.holder == dontRewrite.getFirst() && method.name == dontRewrite.getSecond()) {
@@ -726,7 +610,8 @@
           }
         }
         emulationMethods.add(
-            method.toEmulateInterfaceLibraryMethod(
+            DexEncodedMethod.toEmulateDispatchLibraryMethod(
+                method.method.holder,
                 emulateInterfaceLibraryMethod(method.method, method.method.holder, factory),
                 companionMethod,
                 libraryMethod,
@@ -1041,7 +926,7 @@
 
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
-    synthesizedMethods.addAll(processClasses(builder, flavour));
+    processClasses(builder, flavour, synthesizedMethods::add);
 
     // Process interfaces, create companion or dispatch class if needed, move static
     // methods to companion class, copy default interface methods to companion classes,
@@ -1095,14 +980,17 @@
     return processor.syntheticClasses;
   }
 
-  private Set<DexEncodedMethod> processClasses(Builder<?> builder, Flavor flavour) {
-    ClassProcessor processor = new ClassProcessor(appView, this);
+  private void processClasses(
+      Builder<?> builder, Flavor flavour, Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
+    ClassProcessor processor = new ClassProcessor(appView, this, newSynthesizedMethodConsumer);
+    // First we compute all desugaring *without* introducing forwarding methods.
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, false)) {
-        processor.process(clazz);
+        processor.processClass(clazz);
       }
     }
-    return processor.getForwardMethods();
+    // Then we introduce forwarding methods.
+    processor.addSyntheticMethods();
   }
 
   final boolean isDefaultMethod(DexEncodedMethod method) {
@@ -1125,6 +1013,37 @@
     return true;
   }
 
+  public static boolean isEmulatedInterfaceDispatch(AppView<?> appView, DexEncodedMethod method) {
+    // Answers true if this method is already managed through emulated interface dispatch.
+    Map<DexType, DexType> emulateLibraryInterface =
+        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+    if (emulateLibraryInterface.isEmpty()) {
+      return false;
+    }
+    DexMethod methodToFind = method.method;
+
+    // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
+    // the method, answers true.
+    LinkedList<DexType> workList = new LinkedList<>();
+    workList.add(methodToFind.holder);
+    while (!workList.isEmpty()) {
+      DexType dexType = workList.removeFirst();
+      DexClass dexClass = appView.definitionFor(dexType);
+      assert dexClass != null; // It is a library class, or we are doing L8 compilation.
+      if (dexClass.isInterface() && emulateLibraryInterface.containsKey(dexType)) {
+        DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(methodToFind);
+        if (dexEncodedMethod != null) {
+          return true;
+        }
+      }
+      Collections.addAll(workList, dexClass.interfaces.values);
+      if (dexClass.superType != appView.dexItemFactory().objectType) {
+        workList.add(dexClass.superType);
+      }
+    }
+    return false;
+  }
+
   public void warnMissingInterface(
       DexClass classToDesugar, DexClass implementing, DexType missing) {
     // We use contains() on non hashed collection, but we know it's a 8 cases collection.
@@ -1138,6 +1057,7 @@
     // Companion/Emulated interface/Conversion classes for desugared library won't be missing,
     // they are in the desugared library.
     if (appView.rewritePrefix.hasRewrittenType(missing)
+        || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(missing)
         || appView
             .options()
             .desugaredLibraryConfiguration
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 8b8ef05..1c20983 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -62,7 +62,6 @@
 
   void process(DexProgramClass iface, NestedGraphLense.Builder graphLensBuilder) {
     assert iface.isInterface();
-
     // The list of methods to be created in companion class.
     List<DexEncodedMethod> companionMethods = new ArrayList<>();
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
new file mode 100644
index 0000000..6e109dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
@@ -0,0 +1,43 @@
+// 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.desugar;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+class LambdaBridgeMethodSynthesizedCode extends LambdaSynthesizedCode {
+
+  private final DexMethod mainMethod;
+  private final DexMethod bridgeMethod;
+
+  LambdaBridgeMethodSynthesizedCode(
+      LambdaClass lambda, DexMethod mainMethod, DexMethod bridgeMethod) {
+    super(lambda);
+    this.mainMethod = mainMethod;
+    this.bridgeMethod = bridgeMethod;
+  }
+
+  @Override
+  public SourceCodeProvider getSourceCodeProvider() {
+    return callerPosition ->
+        new LambdaBridgeMethodSourceCode(lambda, mainMethod, bridgeMethod, callerPosition);
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return registry -> {
+      registry.registerInvokeVirtual(mainMethod);
+
+      DexType bridgeMethodReturnType = bridgeMethod.proto.returnType;
+      if (!bridgeMethodReturnType.isVoidType()
+          && bridgeMethodReturnType != mainMethod.proto.returnType
+          && bridgeMethodReturnType != dexItemFactory().objectType) {
+        registry.registerCheckCast(bridgeMethodReturnType);
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 9998c94..e7ba0b5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -62,7 +62,6 @@
   final LambdaDescriptor descriptor;
   final DexMethod constructor;
   final DexMethod classConstructor;
-  final DexMethod createInstanceMethod;
   final DexField lambdaField;
   final Target target;
   final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
@@ -98,13 +97,6 @@
         !stateless
             ? null
             : factory.createField(lambdaClassType, lambdaClassType, rewriter.instanceFieldName);
-    this.createInstanceMethod =
-        stateless
-            ? null
-            : factory.createMethod(
-                lambdaClassType,
-                factory.createProto(lambdaClassType, descriptor.captures.values),
-                rewriter.createInstanceMethodName);
   }
 
   // Generate unique lambda class type for lambda descriptor and instantiation point context.
@@ -137,11 +129,6 @@
     return lazyDexClass.get();
   }
 
-  DexMethod getCreateInstanceMethod() {
-    assert createInstanceMethod != null;
-    return createInstanceMethod;
-  }
-
   private DexProgramClass synthesizeLambdaClass() {
     DexMethod mainMethod =
         rewriter.factory.createMethod(type, descriptor.erasedProto, descriptor.name);
@@ -169,9 +156,7 @@
             synthesizeVirtualMethods(mainMethod),
             rewriter.factory.getSkipNameValidationForTesting(),
             LambdaClass::computeChecksumForSynthesizedClass);
-    // Optimize main method.
     rewriter.converter.appView.appInfo().addSynthesizedClass(clazz);
-    rewriter.converter.optimizeSynthesizedMethod(clazz.lookupVirtualMethod(mainMethod));
 
     // The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
     // ModificationException we must use synchronization.
@@ -232,9 +217,7 @@
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(
-                callerPosition ->
-                    new LambdaMainMethodSourceCode(this, mainMethod, callerPosition)));
+            new LambdaMainMethodSynthesizedCode(this, mainMethod));
 
     // Synthesize bridge methods.
     for (DexProto bridgeProto : descriptor.bridges) {
@@ -250,10 +233,7 @@
                   false),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new SynthesizedCode(
-                  callerPosition ->
-                      new LambdaBridgeMethodSourceCode(
-                          this, mainMethod, bridgeMethod, callerPosition)));
+              new LambdaBridgeMethodSynthesizedCode(this, mainMethod, bridgeMethod));
     }
     return methods;
   }
@@ -261,10 +241,7 @@
   // Synthesize direct methods.
   private DexEncodedMethod[] synthesizeDirectMethods() {
     boolean stateless = isStateless();
-    boolean enableStatefulLambdaCreateInstanceMethod =
-        rewriter.converter.appView.options().testing.enableStatefulLambdaCreateInstanceMethod;
-    DexEncodedMethod[] methods =
-        new DexEncodedMethod[(stateless || enableStatefulLambdaCreateInstanceMethod) ? 2 : 1];
+    DexEncodedMethod[] methods = new DexEncodedMethod[stateless ? 2 : 1];
 
     // Constructor.
     methods[0] =
@@ -276,8 +253,7 @@
                 true),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(
-                callerPosition -> new LambdaConstructorSourceCode(this, callerPosition)));
+            new LambdaConstructorSynthesizedCode(this));
 
     // Class constructor for stateless lambda classes.
     if (stateless) {
@@ -288,18 +264,7 @@
                   Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new SynthesizedCode(
-                  callerPosition -> new LambdaClassConstructorSourceCode(this, callerPosition)));
-    } else if (enableStatefulLambdaCreateInstanceMethod) {
-      methods[1] =
-          new DexEncodedMethod(
-              createInstanceMethod,
-              MethodAccessFlags.fromSharedAccessFlags(
-                  Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false),
-              DexAnnotationSet.empty(),
-              ParameterAnnotationsList.empty(),
-              new SynthesizedCode(
-                  callerPosition -> new LambdaCreateInstanceSourceCode(this, callerPosition)));
+              new LambdaClassConstructorSynthesizedCode(this));
     }
     return methods;
   }
@@ -312,7 +277,7 @@
     for (int i = 0; i < fieldCount; i++) {
       FieldAccessFlags accessFlags =
           FieldAccessFlags.fromSharedAccessFlags(
-              Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PRIVATE);
+              Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC);
       fields[i] = new DexEncodedField(
           getCaptureField(i), accessFlags, DexAnnotationSet.empty(), null);
     }
@@ -518,7 +483,7 @@
     }
 
     // Ensure access of the referenced symbol(s).
-    abstract boolean ensureAccessibility();
+    abstract void ensureAccessibility();
 
     DexClass definitionFor(DexType type) {
       return rewriter.converter.appView.appInfo().app().definitionFor(type);
@@ -563,9 +528,7 @@
     }
 
     @Override
-    boolean ensureAccessibility() {
-      return true;
-    }
+    void ensureAccessibility() {}
   }
 
   // Used for static private lambda$ methods. Only needs access relaxation.
@@ -576,7 +539,7 @@
     }
 
     @Override
-    boolean ensureAccessibility() {
+    void ensureAccessibility() {
       // We already found the static method to be called, just relax its accessibility.
       assert descriptor.getAccessibility() != null;
       descriptor.getAccessibility().unsetPrivate();
@@ -584,7 +547,6 @@
       if (implMethodHolder.isInterface()) {
         descriptor.getAccessibility().setPublic();
       }
-      return true;
     }
   }
 
@@ -597,7 +559,7 @@
     }
 
     @Override
-    boolean ensureAccessibility() {
+    void ensureAccessibility() {
       // For all instantiation points for which the compiler creates lambda$
       // methods, it creates these methods in the same class/interface.
       DexMethod implMethod = descriptor.implHandle.asMethod();
@@ -628,12 +590,11 @@
           DexEncodedMethod.setDebugInfoWithFakeThisParameter(
               newMethod.getCode(), callTarget.getArity(), rewriter.converter.appView);
           implMethodHolder.setDirectMethod(i, newMethod);
-          return true;
+          return;
         }
       }
       assert false
           : "Unexpected failure to find direct lambda target for: " + implMethod.qualifiedName();
-      return false;
     }
   }
 
@@ -645,7 +606,7 @@
     }
 
     @Override
-    boolean ensureAccessibility() {
+    void ensureAccessibility() {
       // For all instantiation points for which the compiler creates lambda$
       // methods, it creates these methods in the same class/interface.
       DexMethod implMethod = descriptor.implHandle.asMethod();
@@ -672,10 +633,9 @@
           // Move the method from the direct methods to the virtual methods set.
           implMethodHolder.removeDirectMethod(i);
           implMethodHolder.appendVirtualMethod(newMethod);
-          return true;
+          return;
         }
       }
-      return false;
     }
   }
 
@@ -688,7 +648,7 @@
     }
 
     @Override
-    boolean ensureAccessibility() {
+    void ensureAccessibility() {
       // Create a static accessor with proper accessibility.
       DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
       assert accessorClass != null;
@@ -715,7 +675,6 @@
       }
 
       rewriter.converter.optimizeSynthesizedMethod(accessorEncodedMethod);
-      return true;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
new file mode 100644
index 0000000..2dbac91
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
@@ -0,0 +1,29 @@
+// 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.desugar;
+
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+class LambdaClassConstructorSynthesizedCode extends LambdaSynthesizedCode {
+
+  LambdaClassConstructorSynthesizedCode(LambdaClass lambda) {
+    super(lambda);
+  }
+
+  @Override
+  public SourceCodeProvider getSourceCodeProvider() {
+    return callerPosition -> new LambdaClassConstructorSourceCode(lambda, callerPosition);
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return registry -> {
+      registry.registerNewInstance(lambda.type);
+      registry.registerInvokeDirect(lambda.constructor);
+      registry.registerStaticFieldWrite(lambda.lambdaField);
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
new file mode 100644
index 0000000..29fb5c8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
@@ -0,0 +1,32 @@
+// 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.desugar;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+class LambdaConstructorSynthesizedCode extends LambdaSynthesizedCode {
+
+  LambdaConstructorSynthesizedCode(LambdaClass lambda) {
+    super(lambda);
+  }
+
+  @Override
+  public SourceCodeProvider getSourceCodeProvider() {
+    return callerPosition -> new LambdaConstructorSourceCode(lambda, callerPosition);
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return registry -> {
+      registry.registerInvokeDirect(lambda.rewriter.objectInitMethod);
+      DexType[] capturedTypes = captures();
+      for (int i = 0; i < capturedTypes.length; i++) {
+        registry.registerInstanceFieldWrite(lambda.getCaptureField(i));
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaCreateInstanceSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaCreateInstanceSourceCode.java
deleted file mode 100644
index eacad03..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaCreateInstanceSourceCode.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import java.util.ArrayList;
-import java.util.List;
-
-// Source code for the static method in a stateful lambda class which creates and initializes
-// a new instance.
-final class LambdaCreateInstanceSourceCode extends SynthesizedLambdaSourceCode {
-
-  LambdaCreateInstanceSourceCode(LambdaClass lambda, Position callerPosition) {
-    super(lambda, lambda.createInstanceMethod, callerPosition, null);
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    // Create and initialize an instance.
-    int instance = nextRegister(ValueType.OBJECT);
-    add(builder -> builder.addNewInstance(instance, lambda.type));
-    int paramCount = proto.parameters.values.length;
-    List<ValueType> types = new ArrayList<>(paramCount + 1);
-    List<Integer> registers = new ArrayList<>(paramCount + 1);
-    types.add(ValueType.OBJECT);
-    registers.add(instance);
-    for (int i = 0; i < paramCount; ++i) {
-      types.add(ValueType.fromDexType(proto.parameters.values[i]));
-      registers.add(getParamRegister(i));
-    }
-    add(
-        builder ->
-            builder.addInvoke(
-                Invoke.Type.DIRECT,
-                lambda.constructor,
-                lambda.constructor.proto,
-                types,
-                registers,
-                false /* isInterface */));
-    add(builder -> builder.addReturn(instance));
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
new file mode 100644
index 0000000..3e7e3f5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
@@ -0,0 +1,62 @@
+// 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.desugar;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.Invoke;
+import java.util.function.Consumer;
+
+class LambdaMainMethodSynthesizedCode extends LambdaSynthesizedCode {
+
+  private final DexMethod mainMethod;
+
+  LambdaMainMethodSynthesizedCode(LambdaClass lambda, DexMethod mainMethod) {
+    super(lambda);
+    this.mainMethod = mainMethod;
+  }
+
+  @Override
+  public SourceCodeProvider getSourceCodeProvider() {
+    return callerPosition -> new LambdaMainMethodSourceCode(lambda, mainMethod, callerPosition);
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return registry -> {
+      LambdaClass.Target target = lambda.target;
+      assert target.invokeType == Invoke.Type.STATIC
+          || target.invokeType == Invoke.Type.VIRTUAL
+          || target.invokeType == Invoke.Type.DIRECT
+          || target.invokeType == Invoke.Type.INTERFACE;
+
+      registry.registerNewInstance(target.callTarget.holder);
+
+      DexType[] capturedTypes = captures();
+      for (int i = 0; i < capturedTypes.length; i++) {
+        registry.registerInstanceFieldRead(lambda.getCaptureField(i));
+      }
+
+      switch (target.invokeType) {
+        case DIRECT:
+          registry.registerInvokeDirect(target.callTarget);
+          break;
+        case INTERFACE:
+          registry.registerInvokeInterface(target.callTarget);
+          break;
+        case STATIC:
+          registry.registerInvokeStatic(target.callTarget);
+          break;
+        case VIRTUAL:
+          registry.registerInvokeVirtual(target.callTarget);
+          break;
+        default:
+          throw new Unreachable();
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 8cc6d15..ed49900 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DefaultUseRegistry;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -27,18 +28,20 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
@@ -46,6 +49,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
 /**
  * Lambda desugaring rewriter.
@@ -59,8 +63,7 @@
   public static final String LAMBDA_CLASS_NAME_PREFIX = "-$$Lambda$";
   public static final String LAMBDA_GROUP_CLASS_NAME_PREFIX = "-$$LambdaGroup$";
   static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
-  static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
-  static final String LAMBDA_CREATE_INSTANCE_METHOD_NAME = "$$createInstance";
+  private static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
 
   private final AppView<?> appView;
   final IRConverter converter;
@@ -71,7 +74,6 @@
   final DexString constructorName;
   final DexString classConstructorName;
   final DexString instanceFieldName;
-  final DexString createInstanceMethodName;
 
   final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
 
@@ -102,11 +104,47 @@
     this.objectInitMethod = factory.createMethod(factory.objectType, initProto, constructorName);
     this.classConstructorName = factory.createString(Constants.CLASS_INITIALIZER_NAME);
     this.instanceFieldName = factory.createString(LAMBDA_INSTANCE_FIELD_NAME);
-    this.createInstanceMethodName = factory.createString(LAMBDA_CREATE_INSTANCE_METHOD_NAME);
   }
 
-  public void synthesizeLambdaClassesFor(
-      DexEncodedMethod method, LensCodeRewriter lensCodeRewriter) {
+  public void synthesizeLambdaClassesForWave(
+      Collection<DexEncodedMethod> wave,
+      ExecutorService executorService,
+      OptimizationFeedbackDelayed feedback,
+      LensCodeRewriter lensCodeRewriter)
+      throws ExecutionException {
+    Set<DexProgramClass> synthesizedLambdaClasses = Sets.newIdentityHashSet();
+    for (DexEncodedMethod method : wave) {
+      synthesizeLambdaClassesForMethod(method, synthesizedLambdaClasses::add, lensCodeRewriter);
+    }
+
+    if (synthesizedLambdaClasses.isEmpty()) {
+      return;
+    }
+
+    // Record that the static fields on each lambda class are only written inside the static
+    // initializer of the lambdas.
+    Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts = new IdentityHashMap<>();
+    for (DexProgramClass synthesizedLambdaClass : synthesizedLambdaClasses) {
+      DexEncodedMethod clinit = synthesizedLambdaClass.getClassInitializer();
+      if (clinit != null) {
+        for (DexEncodedField field : synthesizedLambdaClass.staticFields()) {
+          writesWithContexts.put(field, ImmutableSet.of(clinit));
+        }
+      }
+    }
+
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    appViewWithLiveness.setAppInfo(
+        appViewWithLiveness.appInfo().withStaticFieldWrites(writesWithContexts));
+
+    converter.optimizeSynthesizedLambdaClasses(synthesizedLambdaClasses, executorService);
+    feedback.updateVisibleOptimizationInfo();
+  }
+
+  public void synthesizeLambdaClassesForMethod(
+      DexEncodedMethod method,
+      Consumer<DexProgramClass> consumer,
+      LensCodeRewriter lensCodeRewriter) {
     if (!method.hasCode() || method.isProcessed()) {
       // Nothing to desugar.
       return;
@@ -129,7 +167,9 @@
             LambdaDescriptor descriptor =
                 inferLambdaDescriptor(lensCodeRewriter.rewriteCallSite(callSite, method));
             if (descriptor != LambdaDescriptor.MATCH_FAILED) {
-              getOrCreateLambdaClass(descriptor, method.method.holder);
+              consumer.accept(
+                  getOrCreateLambdaClass(descriptor, method.method.holder)
+                      .getOrCreateLambdaClass());
             }
           }
         });
@@ -177,34 +217,6 @@
     assert code.isConsistentSSA();
   }
 
-  public void desugarLambda(
-      DexType currentType,
-      InstructionListIterator instructions,
-      InvokeCustom lenseRewrittenInvokeCustom,
-      IRCode code) {
-    LambdaDescriptor descriptor = inferLambdaDescriptor(lenseRewrittenInvokeCustom.getCallSite());
-    if (descriptor == LambdaDescriptor.MATCH_FAILED) {
-      return;
-    }
-
-    // We have a descriptor, get or create lambda class.
-    LambdaClass lambdaClass = getOrCreateLambdaClass(descriptor, currentType);
-    assert lambdaClass != null;
-
-    // We rely on patch performing its work in a way which
-    // keeps `instructions` iterator in valid state so that we can continue iteration.
-    patchInstructionSimple(lambdaClass, code, instructions, lenseRewrittenInvokeCustom);
-  }
-
-  public boolean verifyNoLambdasToDesugar(IRCode code) {
-    for (Instruction instruction : code.instructions()) {
-      assert !instruction.isInvokeCustom()
-          || inferLambdaDescriptor(instruction.asInvokeCustom().getCallSite())
-              == LambdaDescriptor.MATCH_FAILED;
-    }
-    return true;
-  }
-
   /** Remove lambda deserialization methods. */
   public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
     for (DexProgramClass clazz : classes) {
@@ -400,108 +412,44 @@
       return;
     }
 
-    if (!converter.appView.options().testing.enableStatefulLambdaCreateInstanceMethod) {
-      // For stateful lambdas we always create a new instance since we need to pass
-      // captured values to the constructor.
-      //
-      // We replace InvokeCustom instruction with a new NewInstance instruction
-      // instantiating lambda followed by InvokeDirect instruction calling a
-      // constructor on it.
-      //
-      //    original:
-      //      Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
-      //
-      //    result:
-      //      NewInstance   rResult <-  LambdaClass
-      //      Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
-      lambdaInstanceValue.setTypeLattice(
-          lambdaInstanceValue
-              .getTypeLattice()
-              .asReferenceTypeLatticeElement()
-              .asDefinitelyNotNull());
-      NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
-      instructions.replaceCurrentInstruction(newInstance);
-
-      List<Value> arguments = new ArrayList<>();
-      arguments.add(lambdaInstanceValue);
-      arguments.addAll(invoke.arguments()); // Optional captures.
-      InvokeDirect constructorCall =
-          new InvokeDirect(lambdaClass.constructor, null /* no return value */, arguments);
-      instructions.add(constructorCall);
-      constructorCall.setPosition(newInstance.getPosition());
-
-      // If we don't have catch handlers we are done.
-      if (!constructorCall.getBlock().hasCatchHandlers()) {
-        return;
-      }
-
-      // Move the iterator back to position it between the two instructions, split
-      // the block between the two instructions, and copy the catch handlers.
-      instructions.previous();
-      assert instructions.peekNext().isInvokeDirect();
-      BasicBlock currentBlock = newInstance.getBlock();
-      BasicBlock nextBlock = instructions.split(code, blocks);
-      assert !instructions.hasNext();
-      nextBlock.copyCatchHandlers(code, blocks, currentBlock, appView.options());
-    } else {
-      // For stateful lambdas we call the createInstance method.
-      //
-      //    original:
-      //      Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
-      //
-      //    result:
-      //      Invoke-Static rResult <- { rArg0, rArg1, ... }; method void
-      // LambdaClass.createInstance(...)
-      InvokeStatic invokeStatic =
-          new InvokeStatic(
-              lambdaClass.getCreateInstanceMethod(), lambdaInstanceValue, invoke.arguments());
-      instructions.replaceCurrentInstruction(invokeStatic);
-    }
-  }
-
-  // Patches invoke-custom instruction to create or get an instance
-  // of the generated lambda class. Assumes that for stateful lambdas the createInstance method
-  // is enabled so invokeCustom is always replaced by a single instruction.
-  private void patchInstructionSimple(
-      LambdaClass lambdaClass,
-      IRCode code,
-      InstructionListIterator instructions,
-      InvokeCustom invoke) {
-    assert lambdaClass != null;
-    assert instructions != null;
-
-    // The value representing new lambda instance: we reuse the value from the original
-    // invoke-custom instruction, and thus all its usages.
-    Value lambdaInstanceValue = invoke.outValue();
-    if (lambdaInstanceValue == null) {
-      // The out value might be empty in case it was optimized out.
-      lambdaInstanceValue =
-          code.createValue(
-              TypeLatticeElement.fromDexType(lambdaClass.type, Nullability.maybeNull(), appView));
-    }
-
-    // For stateless lambdas we replace InvokeCustom instruction with StaticGet reading the value of
-    // INSTANCE field created for singleton lambda class.
-    if (lambdaClass.isStateless()) {
-      instructions.replaceCurrentInstruction(
-          new StaticGet(lambdaInstanceValue, lambdaClass.lambdaField));
-      // Note that since we replace one throwing operation with another we don't need
-      // to have any special handling for catch handlers.
-      return;
-    }
-
-    assert appView.options().testing.enableStatefulLambdaCreateInstanceMethod;
-    // For stateful lambdas we call the createInstance method.
+    // For stateful lambdas we always create a new instance since we need to pass
+    // captured values to the constructor.
+    //
+    // We replace InvokeCustom instruction with a new NewInstance instruction
+    // instantiating lambda followed by InvokeDirect instruction calling a
+    // constructor on it.
     //
     //    original:
     //      Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
     //
     //    result:
-    //      Invoke-Static rResult <- { rArg0, rArg1, ... }; method void
-    // LambdaClass.createInstance(...)
-    InvokeStatic invokeStatic =
-        new InvokeStatic(
-            lambdaClass.getCreateInstanceMethod(), lambdaInstanceValue, invoke.arguments());
-    instructions.replaceCurrentInstruction(invokeStatic);
+    //      NewInstance   rResult <-  LambdaClass
+    //      Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
+    lambdaInstanceValue.setTypeLattice(
+        lambdaInstanceValue.getTypeLattice().asReferenceTypeLatticeElement().asDefinitelyNotNull());
+    NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
+    instructions.replaceCurrentInstruction(newInstance);
+
+    List<Value> arguments = new ArrayList<>();
+    arguments.add(lambdaInstanceValue);
+    arguments.addAll(invoke.arguments()); // Optional captures.
+    InvokeDirect constructorCall =
+        new InvokeDirect(lambdaClass.constructor, null /* no return value */, arguments);
+    instructions.add(constructorCall);
+    constructorCall.setPosition(newInstance.getPosition());
+
+    // If we don't have catch handlers we are done.
+    if (!constructorCall.getBlock().hasCatchHandlers()) {
+      return;
+    }
+
+    // Move the iterator back to position it between the two instructions, split
+    // the block between the two instructions, and copy the catch handlers.
+    instructions.previous();
+    assert instructions.peekNext().isInvokeDirect();
+    BasicBlock currentBlock = newInstance.getBlock();
+    BasicBlock nextBlock = instructions.split(code, blocks);
+    assert !instructions.hasNext();
+    nextBlock.copyCatchHandlers(code, blocks, currentBlock, appView.options());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
new file mode 100644
index 0000000..2565687
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
@@ -0,0 +1,42 @@
+// 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.desugar;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import java.util.function.Consumer;
+
+abstract class LambdaSynthesizedCode extends SynthesizedCode {
+
+  final LambdaClass lambda;
+
+  LambdaSynthesizedCode(LambdaClass lambda) {
+    super(null);
+    this.lambda = lambda;
+  }
+
+  final DexItemFactory dexItemFactory() {
+    return lambda.rewriter.factory;
+  }
+
+  final LambdaDescriptor descriptor() {
+    return lambda.descriptor;
+  }
+
+  final DexType[] captures() {
+    DexTypeList captures = descriptor().captures;
+    assert captures != null;
+    return captures.values;
+  }
+
+  @Override
+  public abstract SourceCodeProvider getSourceCodeProvider();
+
+  @Override
+  public abstract Consumer<UseRegistry> getRegistryCallback();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 864099b..ddfb1c5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -4888,6 +4888,46 @@
         ImmutableList.of());
   }
 
+  public static CfCode StreamMethods_ofNullable(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        1,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfIf(If.Type.NE, ValueType.OBJECT, label1),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/util/stream/Stream;")),
+                    options.itemFactory.createString("empty")),
+                true),
+            new CfGoto(label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/util/stream/Stream;"),
+                        options.itemFactory.createType("Ljava/lang/Object;")),
+                    options.itemFactory.createString("of")),
+                true),
+            label2,
+            new CfReturn(ValueType.OBJECT),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode StringMethods_joinArray(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java b/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java
index dfabec5..334a606 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Assumer.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.Keep;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -14,9 +13,6 @@
 /**
  * One that assumes. Inherited tracker/optimization insert necessary variants of {@link Assume}.
  */
-// TODO(b/143590191): should not need an explicit keep annotation to prevent the default interface
-//  method from being shrunk.
-@Keep
 public interface Assumer {
   default void insertAssumeInstructions(IRCode code) {
     insertAssumeInstructionsInBlocks(code, code.listIterator(), Predicates.alwaysTrue());
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 49b11a5..e83a17a 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
@@ -93,7 +93,7 @@
           }
           // If the resolution ended up with a single target, check if it is a library override.
           // And if so, bail out early (to avoid expensive target lookup).
-          if (resolutionResult.hasSingleTarget()
+          if (resolutionResult.isSingleResolution()
               && isLibraryMethodOrLibraryMethodOverride(resolutionResult.getSingleTarget())) {
             continue;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 6cae242..8c07d3d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -29,8 +28,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -138,9 +135,6 @@
                   && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
                 knownToBeNonNullValues.add(outValue);
               }
-
-              assert verifyCompanionClassInstanceIsKnownToBeNonNull(
-                  fieldInstruction, encodedField, knownToBeNonNullValues);
             }
           }
         }
@@ -246,33 +240,6 @@
     }
   }
 
-  private boolean verifyCompanionClassInstanceIsKnownToBeNonNull(
-      FieldInstruction instruction,
-      DexEncodedField encodedField,
-      Set<Value> knownToBeNonNullValues) {
-    if (!appView.appInfo().hasLiveness()) {
-      return true;
-    }
-    if (instruction.isStaticGet()) {
-      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      DexField field = encodedField.field;
-      DexClass clazz = appViewWithLiveness.definitionFor(field.holder);
-      assert clazz != null;
-      if (clazz.accessFlags.isFinal()
-          && !clazz.initializationOfParentTypesMayHaveSideEffects(appViewWithLiveness)) {
-        DexEncodedMethod classInitializer = clazz.getClassInitializer();
-        if (classInitializer != null) {
-          ClassInitializerInfo info =
-              classInitializer.getOptimizationInfo().getClassInitializerInfo();
-          boolean expectedToBeNonNull =
-              info != null && info.field == field && !appViewWithLiveness.appInfo().isPinned(field);
-          assert !expectedToBeNonNull || knownToBeNonNullValues.contains(instruction.outValue());
-        }
-      }
-    }
-    return true;
-  }
-
   private void addNonNullForValues(
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index ddb7e99..18c56e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.InliningOracle;
+import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.logging.Log;
@@ -35,9 +36,11 @@
 
   enum EligibilityStatus {
     // Used by InlineCandidateProcessor#isInstanceEligible
-    UNUSED_INSTANCE,
     NON_CLASS_TYPE,
+    NOT_A_SINGLETON_FIELD,
+    RETRIEVAL_MAY_HAVE_SIDE_EFFECTS,
     UNKNOWN_TYPE,
+    UNUSED_INSTANCE,
 
     // Used by isClassEligible
     NON_PROGRAM_CLASS,
@@ -235,7 +238,19 @@
         }
 
         // Inline the class instance.
-        anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
+        try {
+          anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
+        } catch (IllegalClassInlinerStateException e) {
+          // We introduced a user that we cannot handle in the class inliner as a result of force
+          // inlining. Abort gracefully from class inlining without removing the instance.
+          //
+          // Alternatively we would need to collect additional information about the behavior of
+          // methods (which is bad for memory), or we would need to analyze the called methods
+          // before inlining them. The latter could be good solution, since we are going to build IR
+          // for the methods that need to be inlined anyway.
+          assert appView.options().testing.allowClassInlinerGracefulExit;
+          anyInlinedMethods = true;
+        }
 
         assert inliningIRProvider.verifyIRCacheIsEmpty();
 
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 436f81b..9afba98 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
@@ -11,10 +11,12 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -29,6 +31,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
@@ -37,9 +40,9 @@
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner.EligibilityStatus;
+import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
@@ -136,9 +139,23 @@
     if (eligibleInstance == null) {
       return EligibilityStatus.UNUSED_INSTANCE;
     }
-
-    eligibleClass =
-        root.isNewInstance() ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+    if (root.isNewInstance()) {
+      eligibleClass = root.asNewInstance().clazz;
+    } else {
+      assert root.isStaticGet();
+      StaticGet staticGet = root.asStaticGet();
+      if (staticGet.instructionMayHaveSideEffects(appView, method.method.holder)) {
+        return EligibilityStatus.RETRIEVAL_MAY_HAVE_SIDE_EFFECTS;
+      }
+      DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
+      FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+      ClassTypeLatticeElement dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
+      if (dynamicLowerBoundType == null
+          || !dynamicLowerBoundType.equals(optimizationInfo.getDynamicUpperBoundType())) {
+        return EligibilityStatus.NOT_A_SINGLETON_FIELD;
+      }
+      eligibleClass = dynamicLowerBoundType.getClassType();
+    }
     if (!eligibleClass.isClassType()) {
       return EligibilityStatus.NON_CLASS_TYPE;
     }
@@ -180,11 +197,13 @@
       // TrivialInstanceInitializer. This will be checked in areInstanceUsersEligible(...).
 
       // There must be no static initializer on the class itself.
-      if (eligibleClassDefinition.hasClassInitializer()) {
+      if (eligibleClassDefinition.classInitializationMayHaveSideEffects(
+          appView,
+          // Types that are a super type of the current context are guaranteed to be initialized.
+          type -> appView.isSubtype(method.method.holder, type).isTrue())) {
         return EligibilityStatus.HAS_CLINIT;
-      } else {
-        return EligibilityStatus.ELIGIBLE;
       }
+      return EligibilityStatus.ELIGIBLE;
     }
 
     assert root.isStaticGet();
@@ -244,34 +263,10 @@
     //      of class inlining
     //
 
-    if (eligibleClassDefinition.instanceFields().size() > 0) {
+    if (!eligibleClassDefinition.instanceFields().isEmpty()) {
       return EligibilityStatus.HAS_INSTANCE_FIELDS;
     }
-    if (appView.appInfo().hasSubtypes(eligibleClassDefinition.type)) {
-      assert !eligibleClassDefinition.accessFlags.isFinal();
-      return EligibilityStatus.NON_FINAL_TYPE;
-    }
-
-    // Singleton instance must be initialized in class constructor.
-    DexEncodedMethod classInitializer = eligibleClassDefinition.getClassInitializer();
-    if (classInitializer == null || isProcessedConcurrently.test(classInitializer)) {
-      return EligibilityStatus.NOT_INITIALIZED_AT_INIT;
-    }
-
-    ClassInitializerInfo initializerInfo =
-        classInitializer.getOptimizationInfo().getClassInitializerInfo();
-    DexField instanceField = root.asStaticGet().getField();
-    // Singleton instance field must NOT be pinned.
-    AppInfoWithLiveness appInfo = appView.appInfo();
-    boolean notPinned =
-        initializerInfo != null
-            && initializerInfo.field == instanceField
-            && !appInfo.isPinned(eligibleClassDefinition.lookupStaticField(instanceField).field);
-    if (notPinned) {
-      return EligibilityStatus.ELIGIBLE;
-    } else {
-      return EligibilityStatus.PINNED_FIELD;
-    }
+    return EligibilityStatus.ELIGIBLE;
   }
 
   /**
@@ -340,6 +335,8 @@
                   continue;
                 }
               }
+              assert !isExtraMethodCall(invoke);
+              return user; // Not eligible.
             }
           }
 
@@ -397,7 +394,8 @@
   //
   // Returns `true` if at least one method was inlined.
   boolean processInlining(
-      IRCode code, Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider) {
+      IRCode code, Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider)
+      throws IllegalClassInlinerStateException {
     // Verify that `eligibleInstance` is not aliased.
     assert eligibleInstance == eligibleInstance.getAliasedValue();
     replaceUsagesAsUnusedArgument(code);
@@ -413,15 +411,7 @@
       // Repeat user analysis
       InstructionOrPhi ineligibleUser = areInstanceUsersEligible(defaultOracle);
       if (ineligibleUser != null) {
-        // We introduced a user that we cannot handle in the class inliner as a result of force
-        // inlining. Abort gracefully from class inlining without removing the instance.
-        //
-        // Alternatively we would need to collect additional information about the behavior of
-        // methods (which is bad for memory), or we would need to analyze the called methods before
-        // inlining them. The latter could be good solution, since we are going to build IR for the
-        // methods that need to be inlined anyway.
-        assert appView.options().testing.allowClassInlinerGracefulExit;
-        return true;
+        throw new IllegalClassInlinerStateException();
       }
       assert extraMethodCalls.isEmpty()
           : "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", ");
@@ -465,7 +455,7 @@
   }
 
   private boolean forceInlineDirectMethodInvocations(
-      IRCode code, InliningIRProvider inliningIRProvider) {
+      IRCode code, InliningIRProvider inliningIRProvider) throws IllegalClassInlinerStateException {
     if (methodCallsOnInstance.isEmpty()) {
       return false;
     }
@@ -485,20 +475,33 @@
         for (Instruction instruction : eligibleInstance.uniqueUsers()) {
           if (instruction.isInvokeDirect()) {
             InvokeDirect invoke = instruction.asInvokeDirect();
-            Value receiver = invoke.getReceiver();
-            if (receiver == eligibleInstance) {
-              DexMethod invokedMethod = invoke.getInvokedMethod();
-              if (appView.dexItemFactory().isConstructor(invokedMethod)
-                  && invokedMethod != appView.dexItemFactory().objectMethods.constructor) {
-                methodCallsOnInstance.put(
-                    invoke,
-                    new InliningInfo(
-                        appView.definitionFor(invokedMethod), root.asNewInstance().clazz));
-                break;
-              }
-            } else {
-              assert receiver.getAliasedValue() != eligibleInstance;
+            Value receiver = invoke.getReceiver().getAliasedValue();
+            if (receiver != eligibleInstance) {
+              continue;
             }
+
+            DexMethod invokedMethod = invoke.getInvokedMethod();
+            if (invokedMethod == appView.dexItemFactory().objectMethods.constructor) {
+              continue;
+            }
+
+            if (!appView.dexItemFactory().isConstructor(invokedMethod)) {
+              throw new IllegalClassInlinerStateException();
+            }
+
+            DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
+            if (singleTarget == null
+                || !singleTarget.isInliningCandidate(
+                    method,
+                    Reason.SIMPLE,
+                    appView.appInfo(),
+                    NopWhyAreYouNotInliningReporter.getInstance())) {
+              throw new IllegalClassInlinerStateException();
+            }
+
+            methodCallsOnInstance.put(
+                invoke, new InliningInfo(singleTarget, root.asNewInstance().clazz));
+            break;
           }
         }
         if (!methodCallsOnInstance.isEmpty()) {
@@ -704,29 +707,25 @@
       return new InliningInfo(singleTarget, eligibleClass);
     }
 
-    // If the superclass of the initializer is NOT java.lang.Object, the super class initializer
-    // being called must be classified as TrivialInstanceInitializer.
-    //
-    // NOTE: since we already classified the class as eligible, it does not have
-    //       any class initializers in superclass chain or in superinterfaces, see
-    //       details in ClassInliner::computeClassEligible(...).
-    if (eligibleClassDefinition.superType != appView.dexItemFactory().objectType) {
-      DexClass superClass = appView.definitionFor(eligibleClassDefinition.superType);
-      if (superClass == null || !superClass.isProgramClass()) {
+    // Check that the entire constructor chain can be inlined into the current context.
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexMethod parent = singleTarget.getOptimizationInfo().getInstanceInitializerInfo().getParent();
+    while (parent != dexItemFactory.objectMethods.constructor) {
+      if (parent == null) {
         return null;
       }
-
-      // At this point, we don't know which constructor in the super type that is invoked from the
-      // method. Therefore, we just check if all of the constructors in the super type are trivial.
-      for (DexEncodedMethod method : superClass.directMethods()) {
-        if (method.isInstanceInitializer()) {
-          InstanceInitializerInfo initializerInfo =
-              method.getOptimizationInfo().getInstanceInitializerInfo();
-          if (initializerInfo.receiverMayEscapeOutsideConstructorChain()) {
-            return null;
-          }
-        }
+      DexEncodedMethod encodedParent = appView.definitionFor(parent);
+      if (encodedParent == null) {
+        return null;
       }
+      if (!encodedParent.isInliningCandidate(
+          method,
+          Reason.SIMPLE,
+          appView.appInfo(),
+          NopWhyAreYouNotInliningReporter.getInstance())) {
+        return null;
+      }
+      parent = encodedParent.getOptimizationInfo().getInstanceInitializerInfo().getParent();
     }
 
     return singleTarget.getOptimizationInfo().getClassInlinerEligibility() != null
@@ -857,7 +856,7 @@
     // 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.
     ResolutionResult resolutionResult = appView.appInfo().resolveMethod(callee.holder, callee);
-    if (resolutionResult.hasSingleTarget()
+    if (resolutionResult.isSingleResolution()
         && !resolutionResult.getSingleTarget().isVirtualMethod()) {
       return null;
     }
@@ -869,13 +868,7 @@
       return null; // Don't inline itself.
     }
 
-    if (isDesugaredLambda && !singleTarget.accessFlags.isBridge()) {
-      markSizeForInlining(invoke, singleTarget);
-      return new InliningInfo(singleTarget, eligibleClass);
-    }
-
     MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
-
     ClassInlinerEligibilityInfo eligibility = optimizationInfo.getClassInlinerEligibility();
     if (eligibility == null) {
       return null;
@@ -1146,4 +1139,6 @@
     instruction.inValues().forEach(v -> v.removeUser(instruction));
     instruction.getBlock().removeInstruction(instruction);
   }
+
+  static class IllegalClassInlinerStateException extends Exception {}
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 7ecbf18..06a94a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.google.common.collect.ImmutableSet;
@@ -83,11 +82,6 @@
   }
 
   @Override
-  public ClassInitializerInfo getClassInitializerInfo() {
-    return null;
-  }
-
-  @Override
   public InstanceInitializerInfo getInstanceInitializerInfo() {
     return DefaultInstanceInitializerInfo.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index d9c4083..fd8078d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import java.util.BitSet;
 import java.util.Set;
@@ -59,8 +58,6 @@
 
   Set<DexType> getInitializedClassesOnNormalExit();
 
-  ClassInitializerInfo getClassInitializerInfo();
-
   InstanceInitializerInfo getInstanceInitializerInfo();
 
   boolean isInitializerEnablingJavaAssertions();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index eac8791..614258b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -7,14 +7,24 @@
 import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
 import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
 import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
 import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
 import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
 import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
 import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
 import static com.android.tools.r8.ir.code.Opcodes.GOTO;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
+import static com.android.tools.r8.ir.code.Opcodes.THROW;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -36,22 +46,23 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
 import com.android.tools.r8.kotlin.Kotlin;
@@ -94,7 +105,7 @@
     }
     computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
     computeInitializedClassesOnNormalExit(feedback, method, code);
-    computeInitializerInfo(method, code, feedback);
+    computeInstanceInitializerInfo(method, code, feedback);
     computeMayHaveSideEffects(feedback, method, code);
     computeReturnValueOnlyDependsOnArguments(feedback, method, code);
     computeNonNullParamOrThrow(feedback, method, code);
@@ -157,8 +168,8 @@
           }
         }
         DexField field = insn.asFieldInstruction().getField();
-        if (field.holder == clazz.type && clazz.lookupInstanceField(field) != null) {
-          // Require only accessing instance fields of the *current* class.
+        if (appView.appInfo().resolveFieldOn(clazz, field) != null) {
+          // Require only accessing direct or indirect instance fields of the current class.
           continue;
         }
         return;
@@ -274,11 +285,11 @@
     }
   }
 
-  private void computeInitializerInfo(
+  private void computeInstanceInitializerInfo(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     assert !appView.appInfo().isPinned(method.method);
 
-    if (!method.isInitializer()) {
+    if (!method.isInstanceInitializer()) {
       return;
     }
 
@@ -296,90 +307,12 @@
       return;
     }
 
-    feedback.setInitializerInfo(
+    InstanceInitializerInfo instanceInitializerInfo = analyzeInstanceInitializer(code, clazz);
+    feedback.setInstanceInitializerInfo(
         method,
-        method.isInstanceInitializer()
-            ? computeInstanceInitializerInfo(code, clazz)
-            : computeClassInitializerInfo(code, clazz));
-  }
-
-  // This method defines trivial class initializer as follows:
-  //
-  // ** The initializer may only instantiate an instance of the same class,
-  //    initialize it with a call to a trivial constructor *without* arguments,
-  //    and assign this instance to a static final field of the same class.
-  //
-  private ClassInitializerInfo computeClassInitializerInfo(IRCode code, DexClass clazz) {
-    Value createdSingletonInstance = null;
-    DexField singletonField = null;
-    for (Instruction insn : code.instructions()) {
-      if (insn.isConstNumber()) {
-        continue;
-      }
-
-      if (insn.isConstString()) {
-        if (insn.instructionInstanceCanThrow()) {
-          return null;
-        }
-        continue;
-      }
-
-      if (insn.isReturn()) {
-        continue;
-      }
-
-      if (insn.isAssume()) {
-        continue;
-      }
-
-      if (insn.isNewInstance()) {
-        NewInstance newInstance = insn.asNewInstance();
-        if (createdSingletonInstance != null
-            || newInstance.clazz != clazz.type
-            || insn.outValue() == null) {
-          return null;
-        }
-        createdSingletonInstance = insn.outValue();
-        continue;
-      }
-
-      if (insn.isInvokeDirect()) {
-        InvokeDirect invokedDirect = insn.asInvokeDirect();
-        if (createdSingletonInstance == null
-            || invokedDirect.getReceiver() != createdSingletonInstance) {
-          return null;
-        }
-        DexEncodedMethod callTarget = clazz.lookupDirectMethod(invokedDirect.getInvokedMethod());
-        if (callTarget == null
-            || !callTarget.isInstanceInitializer()
-            || !callTarget.method.proto.parameters.isEmpty()
-            || callTarget.getOptimizationInfo().getInstanceInitializerInfo().isDefaultInfo()) {
-          return null;
-        }
-        continue;
-      }
-
-      if (insn.isStaticPut()) {
-        StaticPut staticPut = insn.asStaticPut();
-        if (singletonField != null
-            || createdSingletonInstance == null
-            || staticPut.value() != createdSingletonInstance) {
-          return null;
-        }
-        DexEncodedField field = clazz.lookupStaticField(staticPut.getField());
-        if (field == null
-            || !field.accessFlags.isStatic()
-            || !field.accessFlags.isFinal()) {
-          return null;
-        }
-        singletonField = field.field;
-        continue;
-      }
-
-      // Other instructions make the class initializer not eligible.
-      return null;
-    }
-    return singletonField == null ? null : new ClassInitializerInfo(singletonField);
+        instanceInitializerInfo != null
+            ? instanceInitializerInfo
+            : DefaultInstanceInitializerInfo.getInstance());
   }
 
   // This method defines trivial instance initializer as follows:
@@ -397,82 +330,190 @@
   // ** Assigns arguments or non-throwing constants to fields of this class.
   //
   // (Note that this initializer does not have to have zero arguments.)
-  private InstanceInitializerInfo computeInstanceInitializerInfo(IRCode code, DexClass clazz) {
+  private InstanceInitializerInfo analyzeInstanceInitializer(IRCode code, DexClass clazz) {
     if (clazz.definesFinalizer(options.itemFactory)) {
       // Defining a finalize method can observe the side-effect of Object.<init> GC registration.
       return null;
     }
+
     NonTrivialInstanceInitializerInfo.Builder builder = NonTrivialInstanceInitializerInfo.builder();
     Value receiver = code.getThis();
-    for (Instruction instruction : code.instructions()) {
-      switch (instruction.opcode()) {
-        case ARGUMENT:
-        case ASSUME:
-        case CONST_NUMBER:
-        case RETURN:
-          break;
+    boolean hasCatchHandler = false;
+    for (BasicBlock block : code.blocks) {
+      if (block.hasCatchHandlers()) {
+        hasCatchHandler = true;
+      }
 
-        case CONST_CLASS:
-        case CONST_STRING:
-        case DEX_ITEM_BASED_CONST_STRING:
-          if (instruction.instructionMayTriggerMethodInvocation(appView, clazz.type)) {
-            return null;
-          }
-          break;
+      for (Instruction instruction : block.getInstructions()) {
+        switch (instruction.opcode()) {
+          case ARGUMENT:
+          case ASSUME:
+          case CONST_NUMBER:
+          case GOTO:
+          case RETURN:
+            break;
 
-        case GOTO:
-          // Trivial goto to the next block.
-          if (!instruction.asGoto().isTrivialGotoToTheNextBlock(code)) {
-            return null;
-          }
-          break;
+          case IF:
+            builder.setInstanceFieldInitializationMayDependOnEnvironment();
+            break;
 
-        case INSTANCE_PUT:
-          {
-            InstancePut instancePut = instruction.asInstancePut();
-            DexEncodedField field = appView.appInfo().resolveFieldOn(clazz, instancePut.getField());
-            if (field == null
-                || instancePut.object() != receiver
-                || (instancePut.value() != receiver && !instancePut.value().isArgument())) {
-              return null;
+          case CHECK_CAST:
+          case CONST_CLASS:
+          case CONST_STRING:
+          case DEX_ITEM_BASED_CONST_STRING:
+          case INSTANCE_OF:
+          case THROW:
+            // These instructions types may raise an exception, which is a side effect. None of the
+            // instructions can trigger class initialization side effects, hence it is not necessary
+            // to mark all fields as potentially being read. Also, none of the instruction types
+            // can cause the receiver to escape.
+            if (instruction.instructionMayHaveSideEffects(appView, clazz.type)) {
+              builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
             }
-          }
-          break;
+            break;
 
-        case INVOKE_DIRECT:
-          {
-            InvokeDirect invoke = instruction.asInvokeDirect();
-            DexMethod invokedMethod = invoke.getInvokedMethod();
-            if (!dexItemFactory.isConstructor(invokedMethod)) {
-              return null;
-            }
-            if (invokedMethod.holder != clazz.superType) {
-              return null;
-            }
-            // java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
-            if (invokedMethod == dexItemFactory.enumMethods.constructor
-                || invokedMethod == dexItemFactory.objectMethods.constructor) {
-              break;
-            }
-            DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
-            if (singleTarget == null
-                || singleTarget.getOptimizationInfo().getInstanceInitializerInfo().isDefaultInfo()
-                || invoke.getReceiver() != receiver) {
-              return null;
-            }
-            for (Value value : invoke.inValues()) {
-              if (value != receiver && !(value.isConstant() || value.isArgument())) {
+          case INSTANCE_GET:
+          case STATIC_GET:
+            {
+              FieldInstruction fieldGet = instruction.asFieldInstruction();
+              DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField());
+              if (field == null) {
                 return null;
               }
+              builder.markFieldAsRead(field);
+              if (fieldGet.instructionMayHaveSideEffects(appView, clazz.type)) {
+                builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+                if (fieldGet.isStaticGet()) {
+                  // It could trigger a class initializer.
+                  builder.markAllFieldsAsRead();
+                }
+              }
             }
-          }
-          break;
+            break;
 
-        default:
-          // Other instructions make the instance initializer not eligible.
-          return null;
+          case INSTANCE_PUT:
+            {
+              InstancePut instancePut = instruction.asInstancePut();
+              DexEncodedField field = appView.appInfo().resolveField(instancePut.getField());
+              if (field == null) {
+                return null;
+              }
+              if (instancePut.object().getAliasedValue() != receiver
+                  || instancePut.instructionInstanceCanThrow(appView, clazz.type).isThrowing()) {
+                builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+              }
+
+              Value value = instancePut.value().getAliasedValue();
+              // TODO(b/142762134): Replace the use of onlyDependsOnArgument() by
+              //  ValueMayDependOnEnvironmentAnalysis.
+              if (!value.onlyDependsOnArgument()) {
+                builder.setInstanceFieldInitializationMayDependOnEnvironment();
+              }
+              if (value == receiver) {
+                builder.setReceiverMayEscapeOutsideConstructorChain();
+              }
+            }
+            break;
+
+          case INVOKE_DIRECT:
+            {
+              InvokeDirect invoke = instruction.asInvokeDirect();
+              DexMethod invokedMethod = invoke.getInvokedMethod();
+              DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
+              if (singleTarget == null) {
+                return null;
+              }
+              if (singleTarget.isInstanceInitializer() && invoke.getReceiver() == receiver) {
+                if (builder.hasParent()) {
+                  return null;
+                }
+                // java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
+                if (invokedMethod == dexItemFactory.enumMethods.constructor
+                    || invokedMethod == dexItemFactory.objectMethods.constructor) {
+                  builder.setParent(invokedMethod);
+                  break;
+                }
+                builder.merge(singleTarget.getOptimizationInfo().getInstanceInitializerInfo());
+                for (int i = 1; i < invoke.arguments().size(); i++) {
+                  Value argument = invoke.arguments().get(i).getAliasedValue();
+                  if (argument == receiver) {
+                    // In the analysis of the parent constructor, we don't consider the non-receiver
+                    // arguments as being aliases of the receiver. Therefore, we explicitly mark
+                    // that the receiver escapes from this constructor.
+                    builder.setReceiverMayEscapeOutsideConstructorChain();
+                  }
+                  if (!argument.onlyDependsOnArgument()) {
+                    // If the parent constructor assigns this argument into a field, then the value
+                    // of the field may depend on the environment.
+                    builder.setInstanceFieldInitializationMayDependOnEnvironment();
+                  }
+                }
+                builder.setParent(invokedMethod);
+              } else {
+                builder
+                    .markAllFieldsAsRead()
+                    .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+                for (Value inValue : invoke.inValues()) {
+                  if (inValue.getAliasedValue() == receiver) {
+                    builder.setReceiverMayEscapeOutsideConstructorChain();
+                    break;
+                  }
+                }
+              }
+            }
+            break;
+
+          case INVOKE_INTERFACE:
+          case INVOKE_STATIC:
+          case INVOKE_VIRTUAL:
+            InvokeMethod invoke = instruction.asInvokeMethod();
+            builder.markAllFieldsAsRead().setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+            for (Value inValue : invoke.inValues()) {
+              if (inValue.getAliasedValue() == receiver) {
+                builder.setReceiverMayEscapeOutsideConstructorChain();
+                break;
+              }
+            }
+            break;
+
+          case NEW_INSTANCE:
+            {
+              NewInstance newInstance = instruction.asNewInstance();
+              if (newInstance.instructionMayHaveSideEffects(appView, clazz.type)) {
+                // It could trigger a class initializer.
+                builder
+                    .markAllFieldsAsRead()
+                    .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+              }
+            }
+            break;
+
+          default:
+            builder
+                .markAllFieldsAsRead()
+                .setInstanceFieldInitializationMayDependOnEnvironment()
+                .setMayHaveOtherSideEffectsThanInstanceFieldAssignments()
+                .setReceiverMayEscapeOutsideConstructorChain();
+            break;
+        }
       }
     }
+
+    // In presence of exceptional control flow, the assignments to the instance fields could depend
+    // on the environment, if there is an instruction that could throw.
+    //
+    // Example:
+    //   void <init>() {
+    //     try {
+    //       throwIfTrue(Environment.STATIC_FIELD);
+    //     } catch (Exception e) {
+    //       this.f = 42;
+    //     }
+    //   }
+    if (hasCatchHandler && builder.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+      builder.setInstanceFieldInitializationMayDependOnEnvironment();
+    }
+
     return builder.build();
   }
 
@@ -780,7 +821,7 @@
     if (!options.enableSideEffectAnalysis) {
       return;
     }
-    if (appView.appInfo().withLiveness().mayHaveSideEffects.containsKey(method.method)) {
+    if (appView.appInfo().mayHaveSideEffects.containsKey(method.method)) {
       return;
     }
     DexType context = method.method.holder;
@@ -794,6 +835,11 @@
         feedback.classInitializerMayBePostponed(method);
       } else if (classInitializerSideEffect.canBePostponed()) {
         feedback.classInitializerMayBePostponed(method);
+      } else {
+        assert !context.isD8R8SynthesizedLambdaClassType()
+                || options.debug
+                || appView.appInfo().hasPinnedInstanceInitializer(context)
+            : "Unexpected observable side effects from lambda `" + context.toSourceString() + "`";
       }
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index a0828e9..330eba6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -233,8 +233,10 @@
   }
 
   @Override
-  public synchronized void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {
-    getMethodOptimizationInfoForUpdating(method).setInitializerInfo(info);
+  public synchronized void setInstanceInitializerInfo(
+      DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {
+    getMethodOptimizationInfoForUpdating(method)
+        .setInstanceInitializerInfo(instanceInitializerInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index e106dc8..2495e58 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
@@ -112,7 +112,8 @@
       DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {}
 
   @Override
-  public void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {}
+  public void setInstanceInitializerInfo(
+      DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {}
 
   @Override
   public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 0178a2d..34a1fb8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
@@ -161,7 +161,8 @@
   }
 
   @Override
-  public void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {
+  public void setInstanceInitializerInfo(
+      DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {
     // Ignored.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 74dd886..d599a38 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -12,9 +12,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import java.util.BitSet;
 import java.util.Set;
@@ -51,7 +49,8 @@
   // class inliner, null value indicates that the method is not eligible.
   private ClassInlinerEligibilityInfo classInlinerEligibility =
       DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
-  private InitializerInfo initializerInfo = null;
+  private InstanceInitializerInfo instanceInitializerInfo =
+      DefaultInstanceInitializerInfo.getInstance();
   private boolean initializerEnablingJavaAssertions =
       DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
   private ParameterUsagesInfo parametersUsages =
@@ -101,7 +100,7 @@
     checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
     triggersClassInitBeforeAnySideEffect = template.triggersClassInitBeforeAnySideEffect;
     classInlinerEligibility = template.classInlinerEligibility;
-    initializerInfo = template.initializerInfo;
+    instanceInitializerInfo = template.instanceInitializerInfo;
     initializerEnablingJavaAssertions = template.initializerEnablingJavaAssertions;
     parametersUsages = template.parametersUsages;
     nonNullParamOrThrow = template.nonNullParamOrThrow;
@@ -172,16 +171,8 @@
   }
 
   @Override
-  public ClassInitializerInfo getClassInitializerInfo() {
-    return initializerInfo != null ? initializerInfo.asClassInitializerInfo() : null;
-  }
-
-  @Override
   public InstanceInitializerInfo getInstanceInitializerInfo() {
-    if (initializerInfo != null) {
-      return initializerInfo.asInstanceInitializerInfo();
-    }
-    return DefaultInstanceInitializerInfo.getInstance();
+    return instanceInitializerInfo;
   }
 
   @Override
@@ -304,8 +295,8 @@
     this.classInlinerEligibility = eligibility;
   }
 
-  void setInitializerInfo(InitializerInfo initializerInfo) {
-    this.initializerInfo = initializerInfo;
+  void setInstanceInitializerInfo(InstanceInitializerInfo instanceInitializerInfo) {
+    this.instanceInitializerInfo = instanceInitializerInfo;
   }
 
   void setInitializerEnablingJavaAssertions() {
@@ -458,7 +449,7 @@
     // classInlinerEligibility: chances are the method is not an instance method anymore.
     classInlinerEligibility = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
     // initializerInfo: the computed initializer info may become invalid.
-    initializerInfo = null;
+    instanceInitializerInfo = null;
     // initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
     initializerEnablingJavaAssertions =
         DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ClassInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ClassInitializerInfo.java
deleted file mode 100644
index 01d1bb0..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ClassInitializerInfo.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.info.initializer;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
-
-/**
- * Defines class trivial initialized, see details in comments {@link
- * MethodOptimizationInfoCollector#computeClassInitializerInfo}.
- */
-public final class ClassInitializerInfo extends InitializerInfo {
-
-  public final DexField field;
-
-  public ClassInitializerInfo(DexField field) {
-    this.field = field;
-  }
-
-  @Override
-  public ClassInitializerInfo asClassInitializerInfo() {
-    return this;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
index e93cda7..ef3ca08 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.info.initializer;
 
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
 
@@ -24,6 +25,11 @@
   }
 
   @Override
+  public DexMethod getParent() {
+    return null;
+  }
+
+  @Override
   public AbstractFieldSet readSet() {
     return UnknownFieldSet.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InitializerInfo.java
deleted file mode 100644
index a4a2ad9..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InitializerInfo.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.info.initializer;
-
-public class InitializerInfo {
-
-  InitializerInfo() {}
-
-  public ClassInitializerInfo asClassInitializerInfo() {
-    return null;
-  }
-
-  public InstanceInitializerInfo asInstanceInitializerInfo() {
-    return null;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
index 24cb9af..5452bce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.info.initializer;
 
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 
-public abstract class InstanceInitializerInfo extends InitializerInfo {
+public abstract class InstanceInitializerInfo {
+
+  public abstract DexMethod getParent();
 
   /**
    * Returns an abstraction of the set of fields that may be as a result of executing this
@@ -50,9 +53,4 @@
   public boolean isDefaultInfo() {
     return false;
   }
-
-  @Override
-  public InstanceInitializerInfo asInstanceInitializerInfo() {
-    return this;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index 7802e12..70017cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.info.initializer;
 
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
@@ -18,11 +19,13 @@
 
   private final int data;
   private final AbstractFieldSet readSet;
+  private final DexMethod parent;
 
-  private NonTrivialInstanceInitializerInfo(int data, AbstractFieldSet readSet) {
+  private NonTrivialInstanceInitializerInfo(int data, AbstractFieldSet readSet, DexMethod parent) {
     assert verifyNoUnknownBits(data);
     this.data = data;
     this.readSet = readSet;
+    this.parent = parent;
   }
 
   private static boolean verifyNoUnknownBits(int data) {
@@ -39,6 +42,11 @@
   }
 
   @Override
+  public DexMethod getParent() {
+    return parent;
+  }
+
+  @Override
   public AbstractFieldSet readSet() {
     return readSet;
   }
@@ -65,9 +73,10 @@
             | NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS
             | RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN;
     private AbstractFieldSet readSet = EmptyFieldSet.getInstance();
+    private DexMethod parent;
 
     private boolean isTrivial() {
-      return data == 0 && readSet.isTop();
+      return data == 0 && readSet.isTop() && parent == null;
     }
 
     public Builder markFieldAsRead(DexEncodedField field) {
@@ -82,16 +91,50 @@
       return this;
     }
 
+    public Builder markFieldsAsRead(AbstractFieldSet otherReadSet) {
+      if (readSet.isTop() || otherReadSet.isBottom()) {
+        return this;
+      }
+      if (otherReadSet.isTop()) {
+        return markAllFieldsAsRead();
+      }
+      ConcreteMutableFieldSet otherConcreteReadSet = otherReadSet.asConcreteFieldSet();
+      if (readSet.isBottom()) {
+        readSet = new ConcreteMutableFieldSet().addAll(otherConcreteReadSet);
+      } else {
+        readSet.asConcreteFieldSet().addAll(otherConcreteReadSet);
+      }
+      return this;
+    }
+
     public Builder markAllFieldsAsRead() {
       readSet = UnknownFieldSet.getInstance();
       return this;
     }
 
+    public Builder merge(InstanceInitializerInfo instanceInitializerInfo) {
+      markFieldsAsRead(instanceInitializerInfo.readSet());
+      if (instanceInitializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
+        setInstanceFieldInitializationMayDependOnEnvironment();
+      }
+      if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+        setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+      }
+      if (instanceInitializerInfo.receiverMayEscapeOutsideConstructorChain()) {
+        setReceiverMayEscapeOutsideConstructorChain();
+      }
+      return this;
+    }
+
     public Builder setInstanceFieldInitializationMayDependOnEnvironment() {
       data &= ~INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT;
       return this;
     }
 
+    public boolean mayHaveOtherSideEffectsThanInstanceFieldAssignments() {
+      return (data & ~NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS) == 0;
+    }
+
     public Builder setMayHaveOtherSideEffectsThanInstanceFieldAssignments() {
       data &= ~NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS;
       return this;
@@ -102,10 +145,20 @@
       return this;
     }
 
+    public boolean hasParent() {
+      return parent != null;
+    }
+
+    public Builder setParent(DexMethod parent) {
+      assert !hasParent();
+      this.parent = parent;
+      return this;
+    }
+
     public InstanceInitializerInfo build() {
       return isTrivial()
           ? DefaultInstanceInitializerInfo.getInstance()
-          : new NonTrivialInstanceInitializerInfo(data, readSet);
+          : new NonTrivialInstanceInitializerInfo(data, readSet, parent);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index acb6c55..9f77f7a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,17 +4,16 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.DescriptorUtils;
-import java.util.ListIterator;
+import java.util.List;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
@@ -42,35 +41,18 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    ListIterator<KmType> superTypeIterator = kmClass.getSupertypes().listIterator();
-    while (superTypeIterator.hasNext()) {
-      KmType kmType = superTypeIterator.next();
-      Box<Boolean> isLive = new Box<>(false);
-      Box<DexType> renamed = new Box<>(null);
-      kmType.accept(new KmTypeVisitor() {
-        @Override
-        public void visitClass(String name) {
-          String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(name);
-          DexType type = appView.dexItemFactory().createType(descriptor);
-          isLive.set(appView.appInfo().isLiveProgramType(type));
-          DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
-          if (renamedType != type) {
-            renamed.set(renamedType);
-          }
-        }
-      });
-      if (!isLive.get()) {
-        superTypeIterator.remove();
-        continue;
+    List<KmType> superTypes = kmClass.getSupertypes();
+    superTypes.clear();
+    for (DexType itfType : clazz.interfaces.values) {
+      KmType kmType = toRenamedKmType(itfType, appView, lens);
+      if (kmType != null) {
+        superTypes.add(kmType);
       }
-      if (renamed.get() != null) {
-        // TODO(b/70169921): need a general util to convert the current clazz's access flag.
-        KmType renamedKmType = new KmType(kmType.getFlags());
-        renamedKmType.visitClass(
-            DescriptorUtils.descriptorToInternalName(renamed.get().toDescriptorString()));
-        superTypeIterator.remove();
-        superTypeIterator.add(renamedKmType);
-      }
+    }
+    assert clazz.superType != null;
+    KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, appView, lens);
+    if (kmTypeForSupertype != null) {
+      superTypes.add(kmTypeForSupertype);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 6429a50..a24055e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import kotlinx.metadata.jvm.KotlinClassHeader;
@@ -13,15 +14,16 @@
 
 public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
 
-  static KotlinClassFacade fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+  static KotlinClassFacade fromKotlinClassMetadata(
+      KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
     assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade;
     KotlinClassMetadata.MultiFileClassFacade multiFileClassFacade =
         (KotlinClassMetadata.MultiFileClassFacade) kotlinClassMetadata;
-    return new KotlinClassFacade(multiFileClassFacade);
+    return new KotlinClassFacade(multiFileClassFacade, clazz);
   }
 
-  private KotlinClassFacade(KotlinClassMetadata.MultiFileClassFacade metadata) {
-    super(metadata);
+  private KotlinClassFacade(KotlinClassMetadata.MultiFileClassFacade metadata, DexClass clazz) {
+    super(metadata, clazz);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index dc3161c..c05b48f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
@@ -30,7 +31,7 @@
     DexAnnotation meta = clazz.annotations.getFirstMatching(kotlin.metadata.kotlinMetadataType);
     if (meta != null) {
       try {
-        return createKotlinInfo(kotlin, clazz, meta);
+        return createKotlinInfo(kotlin, clazz, meta.annotation);
       } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
         reporter.info(
             new StringDiagnostic("Class " + clazz.type.toSourceString()
@@ -44,12 +45,10 @@
     return null;
   }
 
-  private static KotlinInfo createKotlinInfo(
-      Kotlin kotlin,
-      DexClass clazz,
-      DexAnnotation meta) {
+  private static KotlinClassMetadata toKotlinClassMetadata(
+      Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) {
     Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
-    for (DexAnnotationElement element : meta.annotation.elements) {
+    for (DexAnnotationElement element : metadataAnnotation.elements) {
       elementMap.put(element.name, element);
     }
 
@@ -74,20 +73,25 @@
     Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue();
 
     KotlinClassHeader header = new KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi);
-    KotlinClassMetadata kMetadata = KotlinClassMetadata.read(header);
+    return KotlinClassMetadata.read(header);
+  }
+
+  private static KotlinInfo createKotlinInfo(
+      Kotlin kotlin, DexClass clazz, DexEncodedAnnotation metadataAnnotation) {
+    KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, metadataAnnotation);
 
     if (kMetadata instanceof KotlinClassMetadata.Class) {
       return KotlinClass.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
       return KotlinFile.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
-      return KotlinClassFacade.fromKotlinClassMetadata(kMetadata);
+      return KotlinClassFacade.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
-      return KotlinClassPart.fromKotlinClassMetadata(kMetadata);
+      return KotlinClassPart.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
       return KotlinSyntheticClass.fromKotlinClassMetadata(kMetadata, kotlin, clazz);
     } else {
-      throw new MetadataError("unsupported 'k' value: " + k);
+      throw new MetadataError("unsupported 'k' value: " + kMetadata.getHeader().getKind());
     }
   }
 
@@ -129,5 +133,4 @@
       super(cause);
     }
   }
-
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 96c1022..2b1f9bc 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.kotlin;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import kotlinx.metadata.KmPackage;
@@ -15,15 +16,16 @@
 
   private KmPackage kmPackage;
 
-  static KotlinClassPart fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+  static KotlinClassPart fromKotlinClassMetadata(
+      KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
     assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassPart;
     KotlinClassMetadata.MultiFileClassPart multiFileClassPart =
         (KotlinClassMetadata.MultiFileClassPart) kotlinClassMetadata;
-    return new KotlinClassPart(multiFileClassPart);
+    return new KotlinClassPart(multiFileClassPart, clazz);
   }
 
-  private KotlinClassPart(KotlinClassMetadata.MultiFileClassPart metadata) {
-    super(metadata);
+  private KotlinClassPart(KotlinClassMetadata.MultiFileClassPart metadata, DexClass clazz) {
+    super(metadata, clazz);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index ac69eff..bd8d4c0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -17,11 +17,8 @@
   final DexClass clazz;
   boolean isProcessed;
 
-  KotlinInfo(MetadataKind metadata) {
-    this(metadata, null);
-  }
-
   KotlinInfo(MetadataKind metadata, DexClass clazz) {
+    assert clazz != null;
     this.metadata = metadata;
     this.clazz = clazz;
     processMetadata();
@@ -83,6 +80,7 @@
 
   @Override
   public String toString() {
-    return clazz.toSourceString() + ": " + metadata.toString();
+    return (clazz != null ? clazz.toSourceString() : "<null class?!>")
+        + ": " + metadata.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
new file mode 100644
index 0000000..0b061f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -0,0 +1,37 @@
+// 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.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import kotlinx.metadata.KmType;
+
+class KotlinMetadataSynthesizer {
+  static KmType toRenamedKmType(
+      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    DexClass clazz = appView.definitionFor(type);
+    if (clazz == null) {
+      return null;
+    }
+    // For library or classpath class, synthesize @Metadata always.
+    if (clazz.isNotProgramClass()) {
+      KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags());
+      assert type == lens.lookupType(type, appView.dexItemFactory());
+      kmType.visitClass(DescriptorUtils.descriptorToInternalName(type.toDescriptorString()));
+      return kmType;
+    }
+    // From now on, it is a program class. First, make sure it is live.
+    if (!appView.appInfo().isLiveProgramType(type)) {
+      return null;
+    }
+    KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags());
+    DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+    kmType.visitClass(DescriptorUtils.descriptorToInternalName(renamedType.toDescriptorString()));
+    return kmType;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
index fcf1453..a0d4df2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
@@ -152,7 +152,7 @@
             "Unexpected EOF - no debug line positions");
       }
       // Iterate over the debug line number positions:
-      // <from>#<file>,<to>:<debug-line-position>
+      // <from>#<file>,<range>:<debug-line-position>
       // or
       // <from>#<file>:<debug-line-position>
       reader.readUntil(
@@ -223,18 +223,18 @@
       // The range may have a different end than start.
       String fileAndEndRange = original.substring(fileIndexSplit + 1);
       int endRangeCharPosition = fileAndEndRange.indexOf(',');
-      int originalEnd = originalStart;
+      int size = originalStart;
       if (endRangeCharPosition > -1) {
         // The file should be at least one number wide.
         assert endRangeCharPosition > 0;
-        originalEnd = Integer.parseInt(fileAndEndRange.substring(endRangeCharPosition + 1));
+        size = Integer.parseInt(fileAndEndRange.substring(endRangeCharPosition + 1));
       } else {
         endRangeCharPosition = fileAndEndRange.length();
       }
       int fileIndex = Integer.parseInt(fileAndEndRange.substring(0, endRangeCharPosition));
       Source thisFileSource = builder.files.get(fileIndex);
       if (thisFileSource != null) {
-        Range range = new Range(originalStart, originalEnd);
+        Range range = new Range(originalStart, originalStart + size);
         Position position = new Position(thisFileSource, range);
         Position existingPosition = builder.positions.put(target, position);
         assert existingPosition == null
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 1a1d4ef..147c251 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -419,6 +419,16 @@
       }
     }
 
+    public int getFirstLineNumberOfOriginalRange() {
+      if (originalRange == null) {
+        return 0;
+      } else if (originalRange instanceof Integer) {
+        return (int) originalRange;
+      } else {
+        return ((Range) originalRange).from;
+      }
+    }
+
     @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 95fbcc8..84f726d 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -124,6 +124,16 @@
       return name.indexOf(JAVA_PACKAGE_SEPARATOR) != -1;
     }
 
+    public String toUnqualifiedName() {
+      assert isQualified();
+      return name.substring(name.lastIndexOf(JAVA_PACKAGE_SEPARATOR) + 1);
+    }
+
+    public String toHolderFromQualified() {
+      assert isQualified();
+      return name.substring(0, name.lastIndexOf(JAVA_PACKAGE_SEPARATOR));
+    }
+
     public boolean isMethodSignature() {
       return false;
     }
@@ -286,16 +296,6 @@
       return new MethodSignature(toUnqualifiedName(), type, parameters);
     }
 
-    public String toUnqualifiedName() {
-      assert isQualified();
-      return name.substring(name.lastIndexOf(JAVA_PACKAGE_SEPARATOR) + 1);
-    }
-
-    public String toHolderFromQualified() {
-      assert isQualified();
-      return name.substring(0, name.lastIndexOf(JAVA_PACKAGE_SEPARATOR));
-    }
-
     public DexMethod toDexMethod(DexItemFactory factory, DexType clazz) {
       DexType[] paramTypes = new DexType[parameters.length];
       for (int i = 0; i < parameters.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 7d93488..8ea3d93 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -100,7 +100,7 @@
     }
     // If the method does not have a direct renaming, return the resolutions mapping.
     ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
-    if (resolutionResult.hasSingleTarget()) {
+    if (resolutionResult.isSingleResolution()) {
       return renaming.getOrDefault(resolutionResult.getSingleTarget().method, method.name);
     }
     // If resolution fails, the method must be renamed consistently with the targets that give rise
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 92df84f..fab5f50 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -262,11 +262,10 @@
       DexType type, Map<DexReference, MemberNaming> nonPrivateMembers, DexType[] interfaces) {
     for (DexType iface : interfaces) {
       ClassNamingForMapApplier interfaceNaming = seedMapper.getClassNaming(iface);
-      if (interfaceNaming == null) {
-        continue;
+      if (interfaceNaming != null) {
+        interfaceNaming.forAllMemberNaming(
+            memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, true));
       }
-      interfaceNaming.forAllMemberNaming(
-          memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, true));
       DexClass ifaceClass = appView.definitionFor(iface);
       if (ifaceClass != null) {
         addNonPrivateInterfaceMappings(type, nonPrivateMembers, ifaceClass.interfaces.values);
diff --git a/src/main/java/com/android/tools/r8/naming/Range.java b/src/main/java/com/android/tools/r8/naming/Range.java
index 231a143..c91ae99 100644
--- a/src/main/java/com/android/tools/r8/naming/Range.java
+++ b/src/main/java/com/android/tools/r8/naming/Range.java
@@ -12,6 +12,7 @@
   public Range(int from, int to) {
     this.from = from;
     this.to = to;
+    // TODO(b/145897713): Seems like we should be able to assert from <= to.
   }
 
   public boolean contains(int value) {
diff --git a/src/main/java/com/android/tools/r8/retrace/Result.java b/src/main/java/com/android/tools/r8/retrace/Result.java
index 2940b12..12e92d3 100644
--- a/src/main/java/com/android/tools/r8/retrace/Result.java
+++ b/src/main/java/com/android/tools/r8/retrace/Result.java
@@ -6,9 +6,12 @@
 
 import com.android.tools.r8.Keep;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 @Keep
 public abstract class Result<R, RR extends Result<R, RR>> {
 
+  public abstract Stream<R> stream();
+
   public abstract RR forEach(Consumer<R> resultConsumer);
 }
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 b485868..79d9721 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
 import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
-import com.android.tools.r8.retrace.RetraceStackTrace.RetraceResult;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -117,11 +116,22 @@
     try {
       ClassNameMapper classNameMapper =
           ClassNameMapper.mapperFromString(command.proguardMapProducer.get());
-      RetraceBase retraceBase = new RetraceBaseImpl(classNameMapper);
-      RetraceResult result =
-          new RetraceStackTrace(retraceBase, command.stackTrace, command.diagnosticsHandler)
-              .retrace();
-      command.retracedStackTraceConsumer.accept(result.toListOfStrings());
+      RetraceBase retraceBase = RetraceBaseImpl.create(classNameMapper);
+      RetraceCommandLineResult result;
+      if (command.regularExpression != null) {
+        result =
+            new RetraceRegularExpression(
+                    retraceBase,
+                    command.stackTrace,
+                    command.diagnosticsHandler,
+                    command.regularExpression)
+                .retrace();
+      } else {
+        result =
+            new RetraceStackTrace(retraceBase, command.stackTrace, command.diagnosticsHandler)
+                .retrace();
+      }
+      command.retracedStackTraceConsumer.accept(result.getNodes());
     } catch (IOException ex) {
       command.diagnosticsHandler.error(
           new StringDiagnostic("Could not open mapping input stream: " + ex.getMessage()));
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
index c60cd91..9a68706 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
@@ -21,10 +21,14 @@
 
   private final ClassNameMapper classNameMapper;
 
-  RetraceBaseImpl(ClassNameMapper classNameMapper) {
+  private RetraceBaseImpl(ClassNameMapper classNameMapper) {
     this.classNameMapper = classNameMapper;
   }
 
+  public static RetraceBase create(ClassNameMapper classNameMapper) {
+    return new RetraceBaseImpl(classNameMapper);
+  }
+
   @Override
   public RetraceMethodResult retrace(MethodReference methodReference) {
     return retrace(methodReference.getHolderClass()).lookupMethod(methodReference.getMethodName());
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index ee21fd2..25505fe 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -80,7 +80,8 @@
     return mapper != null;
   }
 
-  Stream<Element> stream() {
+  @Override
+  public Stream<Element> stream() {
     return Stream.of(
         new Element(
             this,
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 5385b09..b2692a2 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -13,6 +13,7 @@
 public class RetraceCommand {
 
   final boolean isVerbose;
+  final String regularExpression;
   final DiagnosticsHandler diagnosticsHandler;
   final ProguardMapProducer proguardMapProducer;
   final List<String> stackTrace;
@@ -20,11 +21,13 @@
 
   private RetraceCommand(
       boolean isVerbose,
+      String regularExpression,
       DiagnosticsHandler diagnosticsHandler,
       ProguardMapProducer proguardMapProducer,
       List<String> stackTrace,
       Consumer<List<String>> retracedStackTraceConsumer) {
     this.isVerbose = isVerbose;
+    this.regularExpression = regularExpression;
     this.diagnosticsHandler = diagnosticsHandler;
     this.proguardMapProducer = proguardMapProducer;
     this.stackTrace = stackTrace;
@@ -55,6 +58,7 @@
     private boolean isVerbose;
     private DiagnosticsHandler diagnosticsHandler;
     private ProguardMapProducer proguardMapProducer;
+    private String regularExpression;
     private List<String> stackTrace;
     private Consumer<List<String>> retracedStackTraceConsumer;
 
@@ -79,6 +83,17 @@
     }
 
     /**
+     * Set a regular expression for parsing the incoming text. The Regular expression must not use
+     * naming groups and has special wild cards according to proguard retrace.
+     *
+     * @param regularExpression The regular expression to use.
+     */
+    public Builder setRegularExpression(String regularExpression) {
+      this.regularExpression = regularExpression;
+      return this;
+    }
+
+    /**
      * Set the obfuscated stack trace that is to be retraced.
      *
      * @param stackTrace Stack trace having the top entry(the closest stack to the error) as the
@@ -114,6 +129,7 @@
       }
       return new RetraceCommand(
           isVerbose,
+          regularExpression,
           diagnosticsHandler,
           proguardMapProducer,
           stackTrace,
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommandLineResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommandLineResult.java
new file mode 100644
index 0000000..00f1857
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommandLineResult.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import java.util.List;
+
+public class RetraceCommandLineResult {
+
+  private final List<String> nodes;
+
+  RetraceCommandLineResult(List<String> nodes) {
+    this.nodes = nodes;
+  }
+
+  public List<String> getNodes() {
+    return nodes;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
index 69a561a..d02913e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -7,9 +7,11 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.FieldReference.UnknownFieldReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
@@ -46,7 +48,8 @@
     return memberNamings.size() > 1;
   }
 
-  Stream<Element> stream() {
+  @Override
+  public Stream<Element> stream() {
     if (!hasRetraceResult()) {
       return Stream.of(
           new Element(
@@ -61,12 +64,20 @@
               assert memberNaming.isFieldNaming();
               FieldSignature fieldSignature =
                   memberNaming.getOriginalSignature().asFieldSignature();
+              ClassReference holder =
+                  fieldSignature.isQualified()
+                      ? Reference.classFromDescriptor(
+                          DescriptorUtils.javaTypeToDescriptor(
+                              fieldSignature.toHolderFromQualified()))
+                      : classElement.getClassReference();
               return new Element(
                   this,
                   classElement,
                   Reference.field(
-                      classElement.getClassReference(),
-                      fieldSignature.name,
+                      holder,
+                      fieldSignature.isQualified()
+                          ? fieldSignature.toUnqualifiedName()
+                          : fieldSignature.name,
                       Reference.typeFromTypeName(fieldSignature.type)));
             });
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index 77b4761..d537f03 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -38,6 +38,10 @@
     assert classElement != null;
   }
 
+  public UnknownMethodReference getUnknownReference() {
+    return new UnknownMethodReference(classElement.getClassReference(), obfuscatedName);
+  }
+
   private boolean hasRetraceResult() {
     return mappedRanges != null && mappedRanges.getMappedRanges().size() > 0;
   }
@@ -86,14 +90,10 @@
         classElement, new MappedRangesOfName(narrowedRanges), obfuscatedName);
   }
 
-  Stream<Element> stream() {
+  @Override
+  public Stream<Element> stream() {
     if (!hasRetraceResult()) {
-      return Stream.of(
-          new Element(
-              this,
-              classElement,
-              new UnknownMethodReference(classElement.getClassReference(), obfuscatedName),
-              null));
+      return Stream.of(new Element(this, classElement, getUnknownReference(), null));
     }
     return mappedRanges.getMappedRanges().stream()
         .map(
@@ -161,5 +161,12 @@
     public int getOriginalLineNumber(int linePosition) {
       return mappedRange != null ? mappedRange.getOriginalLineNumber(linePosition) : linePosition;
     }
+
+    public int getFirstLineNumberOfOriginalRange() {
+      if (mappedRange == null) {
+        return 0;
+      }
+      return mappedRange.getFirstLineNumberOfOriginalRange();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
new file mode 100644
index 0000000..d6b4cb9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -0,0 +1,746 @@
+// 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.DiagnosticsHandler;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetraceClassResult.Element;
+import com.android.tools.r8.retrace.RetraceRegularExpression.RetraceString.RetraceStringBuilder;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class RetraceRegularExpression {
+
+  private final RetraceBase retraceBase;
+  private final List<String> stackTrace;
+  private final DiagnosticsHandler diagnosticsHandler;
+  private final String regularExpression;
+
+  private static final int NO_MATCH = -1;
+
+  private final RegularExpressionGroup[] groups =
+      new RegularExpressionGroup[] {
+        new TypeNameGroup(),
+        new BinaryNameGroup(),
+        new MethodNameGroup(),
+        new FieldNameGroup(),
+        new SourceFileGroup(),
+        new LineNumberGroup(),
+        new FieldOrReturnTypeGroup(),
+        new MethodArgumentsGroup()
+      };
+
+  private static final String CAPTURE_GROUP_PREFIX = "captureGroup";
+
+  RetraceRegularExpression(
+      RetraceBase retraceBase,
+      List<String> stackTrace,
+      DiagnosticsHandler diagnosticsHandler,
+      String regularExpression) {
+    this.retraceBase = retraceBase;
+    this.stackTrace = stackTrace;
+    this.diagnosticsHandler = diagnosticsHandler;
+    this.regularExpression = regularExpression;
+  }
+
+  public RetraceCommandLineResult retrace() {
+    List<RegularExpressionGroupHandler> handlers = new ArrayList<>();
+    String regularExpression = registerGroups(this.regularExpression, handlers);
+    Pattern compiledPattern = Pattern.compile(regularExpression);
+    List<String> result = new ArrayList<>();
+    for (String string : stackTrace) {
+      Matcher matcher = compiledPattern.matcher(string);
+      List<RetraceString> retracedStrings =
+          Lists.newArrayList(RetraceStringBuilder.create(string).build());
+      if (matcher.matches()) {
+        for (RegularExpressionGroupHandler handler : handlers) {
+          retracedStrings = handler.handleMatch(retracedStrings, matcher, retraceBase);
+        }
+      }
+      if (retracedStrings.isEmpty()) {
+        // We could not find a match. Output the identity.
+        result.add(string);
+      } else {
+        for (RetraceString retracedString : retracedStrings) {
+          result.add(retracedString.getRetracedString());
+        }
+      }
+    }
+    return new RetraceCommandLineResult(result);
+  }
+
+  private String registerGroups(
+      String regularExpression, List<RegularExpressionGroupHandler> handlers) {
+    int currentIndex = 0;
+    int captureGroupIndex = 0;
+    while (currentIndex < regularExpression.length()) {
+      RegularExpressionGroup firstGroup = null;
+      int firstIndexFromCurrent = regularExpression.length();
+      for (RegularExpressionGroup group : groups) {
+        int nextIndexOf = regularExpression.indexOf(group.shortName(), currentIndex);
+        if (nextIndexOf > NO_MATCH && nextIndexOf < firstIndexFromCurrent) {
+          // Check if previous character in the regular expression is not \\ to ensure not
+          // overriding a matching on shortName.
+          if (nextIndexOf > 0 && regularExpression.charAt(nextIndexOf - 1) == '\\') {
+            continue;
+          }
+          firstGroup = group;
+          firstIndexFromCurrent = nextIndexOf;
+        }
+      }
+      if (firstGroup != null) {
+        String captureGroupName = CAPTURE_GROUP_PREFIX + (captureGroupIndex++);
+        String patternToInsert = "(?<" + captureGroupName + ">" + firstGroup.subExpression() + ")";
+        regularExpression =
+            regularExpression.substring(0, firstIndexFromCurrent)
+                + patternToInsert
+                + regularExpression.substring(
+                    firstIndexFromCurrent + firstGroup.shortName().length());
+        handlers.add(firstGroup.createHandler(captureGroupName));
+        firstIndexFromCurrent += patternToInsert.length();
+      }
+      currentIndex = firstIndexFromCurrent;
+    }
+    return regularExpression;
+  }
+
+  static class RetraceString {
+
+    private final Element classContext;
+    private final ClassNameGroup classNameGroup;
+    private final ClassReference qualifiedContext;
+    private final RetraceMethodResult.Element methodContext;
+    private final TypeReference typeOrReturnTypeContext;
+    private final boolean hasTypeOrReturnTypeContext;
+    private final String retracedString;
+    private final int adjustedIndex;
+
+    private RetraceString(
+        Element classContext,
+        ClassNameGroup classNameGroup,
+        ClassReference qualifiedContext,
+        RetraceMethodResult.Element methodContext,
+        TypeReference typeOrReturnTypeContext,
+        boolean hasTypeOrReturnTypeContext,
+        String retracedString,
+        int adjustedIndex) {
+      this.classContext = classContext;
+      this.classNameGroup = classNameGroup;
+      this.qualifiedContext = qualifiedContext;
+      this.methodContext = methodContext;
+      this.typeOrReturnTypeContext = typeOrReturnTypeContext;
+      this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
+      this.retracedString = retracedString;
+      this.adjustedIndex = adjustedIndex;
+    }
+
+    String getRetracedString() {
+      return retracedString;
+    }
+
+    boolean hasTypeOrReturnTypeContext() {
+      return hasTypeOrReturnTypeContext;
+    }
+
+    Element getClassContext() {
+      return classContext;
+    }
+
+    RetraceMethodResult.Element getMethodContext() {
+      return methodContext;
+    }
+
+    TypeReference getTypeOrReturnTypeContext() {
+      return typeOrReturnTypeContext;
+    }
+
+    public ClassReference getQualifiedContext() {
+      return qualifiedContext;
+    }
+
+    RetraceStringBuilder transform() {
+      return RetraceStringBuilder.create(this);
+    }
+
+    static class RetraceStringBuilder {
+
+      private Element classContext;
+      private ClassNameGroup classNameGroup;
+      private ClassReference qualifiedContext;
+      private RetraceMethodResult.Element methodContext;
+      private TypeReference typeOrReturnTypeContext;
+      private boolean hasTypeOrReturnTypeContext;
+      private String retracedString;
+      private int adjustedIndex;
+
+      private int maxReplaceStringIndex = NO_MATCH;
+
+      private RetraceStringBuilder(
+          Element classContext,
+          ClassNameGroup classNameGroup,
+          ClassReference qualifiedContext,
+          RetraceMethodResult.Element methodContext,
+          TypeReference typeOrReturnTypeContext,
+          boolean hasTypeOrReturnTypeContext,
+          String retracedString,
+          int adjustedIndex) {
+        this.classContext = classContext;
+        this.classNameGroup = classNameGroup;
+        this.qualifiedContext = qualifiedContext;
+        this.methodContext = methodContext;
+        this.typeOrReturnTypeContext = typeOrReturnTypeContext;
+        this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
+        this.retracedString = retracedString;
+        this.adjustedIndex = adjustedIndex;
+      }
+
+      static RetraceStringBuilder create(String string) {
+        return new RetraceStringBuilder(null, null, null, null, null, false, string, 0);
+      }
+
+      static RetraceStringBuilder create(RetraceString string) {
+        return new RetraceStringBuilder(
+            string.classContext,
+            string.classNameGroup,
+            string.qualifiedContext,
+            string.methodContext,
+            string.typeOrReturnTypeContext,
+            string.hasTypeOrReturnTypeContext,
+            string.retracedString,
+            string.adjustedIndex);
+      }
+
+      RetraceStringBuilder setClassContext(Element classContext, ClassNameGroup classNameGroup) {
+        this.classContext = classContext;
+        this.classNameGroup = classNameGroup;
+        return this;
+      }
+
+      RetraceStringBuilder setMethodContext(RetraceMethodResult.Element methodContext) {
+        this.methodContext = methodContext;
+        return this;
+      }
+
+      RetraceStringBuilder setTypeOrReturnTypeContext(TypeReference typeOrReturnTypeContext) {
+        hasTypeOrReturnTypeContext = true;
+        this.typeOrReturnTypeContext = typeOrReturnTypeContext;
+        return this;
+      }
+
+      RetraceStringBuilder setQualifiedContext(ClassReference qualifiedContext) {
+        this.qualifiedContext = qualifiedContext;
+        return this;
+      }
+
+      RetraceStringBuilder replaceInString(String oldString, String newString) {
+        int oldStringStartIndex = retracedString.indexOf(oldString);
+        assert oldStringStartIndex > NO_MATCH;
+        int oldStringEndIndex = oldStringStartIndex + oldString.length();
+        return replaceInStringRaw(newString, oldStringStartIndex, oldStringEndIndex);
+      }
+
+      RetraceStringBuilder replaceInString(String newString, int originalFrom, int originalTo) {
+        return replaceInStringRaw(
+            newString, originalFrom + adjustedIndex, originalTo + adjustedIndex);
+      }
+
+      RetraceStringBuilder replaceInStringRaw(String newString, int from, int to) {
+        assert from <= to;
+        assert from > maxReplaceStringIndex;
+        String prefix = retracedString.substring(0, from);
+        String postFix = retracedString.substring(to);
+        this.retracedString = prefix + newString + postFix;
+        this.adjustedIndex = adjustedIndex + newString.length() - (to - from);
+        maxReplaceStringIndex = prefix.length() + newString.length();
+        return this;
+      }
+
+      RetraceString build() {
+        return new RetraceString(
+            classContext,
+            classNameGroup,
+            qualifiedContext,
+            methodContext,
+            typeOrReturnTypeContext,
+            hasTypeOrReturnTypeContext,
+            retracedString,
+            adjustedIndex);
+      }
+    }
+  }
+
+  private interface RegularExpressionGroupHandler {
+
+    List<RetraceString> handleMatch(
+        List<RetraceString> strings, Matcher matcher, RetraceBase retraceBase);
+  }
+
+  private abstract static class RegularExpressionGroup {
+
+    abstract String shortName();
+
+    abstract String subExpression();
+
+    abstract RegularExpressionGroupHandler createHandler(String captureGroup);
+  }
+
+  // TODO(b/145731185): Extend support for identifiers with strings inside back ticks.
+  private static final String javaIdentifierSegment = "[\\p{L}\\p{N}_\\p{Sc}]+";
+
+  private abstract static class ClassNameGroup extends RegularExpressionGroup {
+
+    abstract String getClassName(ClassReference classReference);
+
+    abstract ClassReference classFromMatch(String match);
+
+    @Override
+    RegularExpressionGroupHandler createHandler(String captureGroup) {
+      return (strings, matcher, retraceBase) -> {
+        if (matcher.start(captureGroup) == NO_MATCH) {
+          return strings;
+        }
+        String typeName = matcher.group(captureGroup);
+        RetraceClassResult retraceResult = retraceBase.retrace(classFromMatch(typeName));
+        List<RetraceString> retracedStrings = new ArrayList<>();
+        for (RetraceString retraceString : strings) {
+          retraceResult.forEach(
+              element -> {
+                retracedStrings.add(
+                    retraceString
+                        .transform()
+                        .setClassContext(element, this)
+                        .setMethodContext(null)
+                        .replaceInString(
+                            getClassName(element.getClassReference()),
+                            matcher.start(captureGroup),
+                            matcher.end(captureGroup))
+                        .build());
+              });
+        }
+        return retracedStrings;
+      };
+    }
+  }
+
+  private static class TypeNameGroup extends ClassNameGroup {
+
+    @Override
+    String shortName() {
+      return "%c";
+    }
+
+    @Override
+    String subExpression() {
+      return "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment;
+    }
+
+    @Override
+    String getClassName(ClassReference classReference) {
+      return classReference.getTypeName();
+    }
+
+    @Override
+    ClassReference classFromMatch(String match) {
+      return Reference.classFromTypeName(match);
+    }
+  }
+
+  private static class BinaryNameGroup extends ClassNameGroup {
+
+    @Override
+    String shortName() {
+      return "%C";
+    }
+
+    @Override
+    String subExpression() {
+      return "(?:" + javaIdentifierSegment + "\\/)*" + javaIdentifierSegment;
+    }
+
+    @Override
+    String getClassName(ClassReference classReference) {
+      return classReference.getBinaryName();
+    }
+
+    @Override
+    ClassReference classFromMatch(String match) {
+      return Reference.classFromBinaryName(match);
+    }
+  }
+
+  private static class MethodNameGroup extends RegularExpressionGroup {
+
+    @Override
+    String shortName() {
+      return "%m";
+    }
+
+    @Override
+    String subExpression() {
+      return javaIdentifierSegment;
+    }
+
+    @Override
+    RegularExpressionGroupHandler createHandler(String captureGroup) {
+      return (strings, matcher, retraceBase) -> {
+        if (matcher.start(captureGroup) == NO_MATCH) {
+          return strings;
+        }
+        String methodName = matcher.group(captureGroup);
+        List<RetraceString> retracedStrings = new ArrayList<>();
+        for (RetraceString retraceString : strings) {
+          if (retraceString.classContext == null) {
+            retracedStrings.add(retraceString);
+            continue;
+          }
+          retraceString
+              .getClassContext()
+              .lookupMethod(methodName)
+              .forEach(
+                  element -> {
+                    MethodReference methodReference = element.getMethodReference();
+                    if (retraceString.hasTypeOrReturnTypeContext()) {
+                      if (methodReference.getReturnType() == null
+                          && retraceString.getTypeOrReturnTypeContext() != null) {
+                        return;
+                      } else if (methodReference.getReturnType() != null
+                          && !methodReference
+                              .getReturnType()
+                              .equals(retraceString.getTypeOrReturnTypeContext())) {
+                        return;
+                      }
+                    }
+                    RetraceStringBuilder newRetraceString = retraceString.transform();
+                    ClassReference existingClass =
+                        retraceString.getClassContext().getClassReference();
+                    ClassReference holder = methodReference.getHolderClass();
+                    if (holder != existingClass) {
+                      // The element is defined on another holder.
+                      newRetraceString
+                          .replaceInString(
+                              newRetraceString.classNameGroup.getClassName(existingClass),
+                              newRetraceString.classNameGroup.getClassName(holder))
+                          .setQualifiedContext(holder);
+                    }
+                    newRetraceString
+                        .setMethodContext(element)
+                        .replaceInString(
+                            methodReference.getMethodName(),
+                            matcher.start(captureGroup),
+                            matcher.end(captureGroup));
+                    retracedStrings.add(newRetraceString.build());
+                  });
+        }
+        return retracedStrings;
+      };
+    }
+  }
+
+  private static class FieldNameGroup extends RegularExpressionGroup {
+
+    @Override
+    String shortName() {
+      return "%f";
+    }
+
+    @Override
+    String subExpression() {
+      return javaIdentifierSegment;
+    }
+
+    @Override
+    RegularExpressionGroupHandler createHandler(String captureGroup) {
+      return (strings, matcher, retraceBase) -> {
+        if (matcher.start(captureGroup) == NO_MATCH) {
+          return strings;
+        }
+        String methodName = matcher.group(captureGroup);
+        List<RetraceString> retracedStrings = new ArrayList<>();
+        for (RetraceString retraceString : strings) {
+          if (retraceString.getClassContext() == null) {
+            retracedStrings.add(retraceString);
+            continue;
+          }
+          retraceString
+              .getClassContext()
+              .lookupField(methodName)
+              .forEach(
+                  element -> {
+                    RetraceStringBuilder newRetraceString = retraceString.transform();
+                    ClassReference existingClass =
+                        retraceString.getClassContext().getClassReference();
+                    ClassReference holder = element.getFieldReference().getHolderClass();
+                    if (holder != existingClass) {
+                      // The element is defined on another holder.
+                      newRetraceString
+                          .replaceInString(
+                              newRetraceString.classNameGroup.getClassName(existingClass),
+                              newRetraceString.classNameGroup.getClassName(holder))
+                          .setQualifiedContext(holder);
+                    }
+                    newRetraceString.replaceInString(
+                        element.getFieldReference().getFieldName(),
+                        matcher.start(captureGroup),
+                        matcher.end(captureGroup));
+                    retracedStrings.add(newRetraceString.build());
+                  });
+        }
+        return retracedStrings;
+      };
+    }
+  }
+
+  private static class SourceFileGroup extends RegularExpressionGroup {
+
+    @Override
+    String shortName() {
+      return "%s";
+    }
+
+    @Override
+    String subExpression() {
+      return "(?:\\w+\\.)*\\w+";
+    }
+
+    @Override
+    RegularExpressionGroupHandler createHandler(String captureGroup) {
+      return (strings, matcher, retraceBase) -> {
+        if (matcher.start(captureGroup) == NO_MATCH) {
+          return strings;
+        }
+        String fileName = matcher.group(captureGroup);
+        List<RetraceString> retracedStrings = new ArrayList<>();
+        for (RetraceString retraceString : strings) {
+          if (retraceString.classContext == null) {
+            retracedStrings.add(retraceString);
+            continue;
+          }
+          String newSourceFile =
+              retraceString.getQualifiedContext() != null
+                  ? retraceBase.retraceSourceFile(
+                      retraceString.classContext.getClassReference(),
+                      fileName,
+                      retraceString.getQualifiedContext(),
+                      true)
+                  : retraceString.classContext.retraceSourceFile(fileName, retraceBase);
+          retracedStrings.add(
+              retraceString
+                  .transform()
+                  .replaceInString(
+                      newSourceFile, matcher.start(captureGroup), matcher.end(captureGroup))
+                  .build());
+        }
+        return retracedStrings;
+      };
+    }
+  }
+
+  private class LineNumberGroup extends RegularExpressionGroup {
+
+    @Override
+    String shortName() {
+      return "%l";
+    }
+
+    @Override
+    String subExpression() {
+      return "\\d*";
+    }
+
+    @Override
+    RegularExpressionGroupHandler createHandler(String captureGroup) {
+      return (strings, matcher, retraceBase) -> {
+        if (matcher.start(captureGroup) == NO_MATCH) {
+          return strings;
+        }
+        String lineNumberAsString = matcher.group(captureGroup);
+        int lineNumber =
+            lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString);
+        List<RetraceString> retracedStrings = new ArrayList<>();
+        for (RetraceString retraceString : strings) {
+          RetraceMethodResult.Element methodContext = retraceString.methodContext;
+          if (methodContext == null) {
+            retracedStrings.add(retraceString);
+            continue;
+          }
+          Set<MethodReference> narrowedSet =
+              methodContext.getRetraceMethodResult().narrowByLine(lineNumber).stream()
+                  .map(RetraceMethodResult.Element::getMethodReference)
+                  .collect(Collectors.toSet());
+          if (!narrowedSet.contains(methodContext.getMethodReference())) {
+            // Prune the retraceString since we now have line number information and this is not
+            // a part of the result.
+            diagnosticsHandler.info(
+                new StringDiagnostic(
+                    "Pruning "
+                        + retraceString.getRetracedString()
+                        + " from result because line number "
+                        + lineNumber
+                        + " does not match."));
+            continue;
+          }
+          String newLineNumber =
+              lineNumber > NO_MATCH
+                  ? methodContext.getOriginalLineNumber(lineNumber) + ""
+                  : lineNumberAsString;
+          retracedStrings.add(
+              retraceString
+                  .transform()
+                  .replaceInString(
+                      newLineNumber, matcher.start(captureGroup), matcher.end(captureGroup))
+                  .build());
+        }
+        return retracedStrings;
+      };
+    }
+  }
+
+  private static final String JAVA_TYPE_REGULAR_EXPRESSION =
+      "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment + "[\\[\\]]*";
+
+  private static class FieldOrReturnTypeGroup extends RegularExpressionGroup {
+
+    @Override
+    String shortName() {
+      return "%t";
+    }
+
+    @Override
+    String subExpression() {
+      return JAVA_TYPE_REGULAR_EXPRESSION;
+    }
+
+    @Override
+    RegularExpressionGroupHandler createHandler(String captureGroup) {
+      return (strings, matcher, retraceBase) -> {
+        if (matcher.start(captureGroup) == NO_MATCH) {
+          return strings;
+        }
+        String typeName = matcher.group(captureGroup);
+        String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName);
+        if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) {
+          return strings;
+        }
+        TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor);
+        List<RetraceString> retracedStrings = new ArrayList<>();
+        RetraceTypeResult retracedType = retraceBase.retrace(typeReference);
+        for (RetraceString retraceString : strings) {
+          retracedType.forEach(
+              element -> {
+                TypeReference retracedReference = element.getTypeReference();
+                retracedStrings.add(
+                    retraceString
+                        .transform()
+                        .setTypeOrReturnTypeContext(retracedReference)
+                        .replaceInString(
+                            retracedReference == null ? "void" : retracedReference.getTypeName(),
+                            matcher.start(captureGroup),
+                            matcher.end(captureGroup))
+                        .build());
+              });
+        }
+        return retracedStrings;
+      };
+    }
+  }
+
+  private class MethodArgumentsGroup extends RegularExpressionGroup {
+
+    @Override
+    String shortName() {
+      return "%a";
+    }
+
+    @Override
+    String subExpression() {
+      return "((" + JAVA_TYPE_REGULAR_EXPRESSION + "\\,)*" + JAVA_TYPE_REGULAR_EXPRESSION + ")?";
+    }
+
+    @Override
+    RegularExpressionGroupHandler createHandler(String captureGroup) {
+      return (strings, matcher, retraceBase) -> {
+        if (matcher.start(captureGroup) == NO_MATCH) {
+          return strings;
+        }
+        Set<List<TypeReference>> initialValue = new LinkedHashSet<>();
+        initialValue.add(new ArrayList<>());
+        Set<List<TypeReference>> allRetracedReferences =
+            Arrays.stream(matcher.group(captureGroup).split(","))
+                .map(String::trim)
+                .reduce(
+                    initialValue,
+                    (acc, typeName) -> {
+                      String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName);
+                      if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) {
+                        return acc;
+                      }
+                      TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor);
+                      Set<List<TypeReference>> retracedTypes = new LinkedHashSet<>();
+                      retraceBase
+                          .retrace(typeReference)
+                          .forEach(
+                              element -> {
+                                for (List<TypeReference> currentReferences : acc) {
+                                  ArrayList<TypeReference> newList =
+                                      new ArrayList<>(currentReferences);
+                                  newList.add(element.getTypeReference());
+                                  retracedTypes.add(newList);
+                                }
+                              });
+                      return retracedTypes;
+                    },
+                    (l1, l2) -> {
+                      l1.addAll(l2);
+                      return l1;
+                    });
+        List<RetraceString> retracedStrings = new ArrayList<>();
+        for (RetraceString retraceString : strings) {
+          if (retraceString.getMethodContext() != null
+              && !allRetracedReferences.contains(
+                  retraceString.getMethodContext().getMethodReference().getFormalTypes())) {
+            // Prune the string since we now know the formals.
+            String formals =
+                retraceString.getMethodContext().getMethodReference().getFormalTypes().stream()
+                    .map(TypeReference::getTypeName)
+                    .collect(Collectors.joining(","));
+            diagnosticsHandler.info(
+                new StringDiagnostic(
+                    "Pruning "
+                        + retraceString.getRetracedString()
+                        + " from result because formals ("
+                        + formals
+                        + ") do not match result set."));
+            continue;
+          }
+          for (List<TypeReference> retracedReferences : allRetracedReferences) {
+            retracedStrings.add(
+                retraceString
+                    .transform()
+                    .replaceInString(
+                        retracedReferences.stream()
+                            .map(TypeReference::getTypeName)
+                            .collect(Collectors.joining(",")),
+                        matcher.start(captureGroup),
+                        matcher.end(captureGroup))
+                    .build());
+          }
+        }
+        return retracedStrings;
+      };
+    }
+  }
+}
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 1f4fa43..7c899a7 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -79,23 +79,6 @@
     }
   }
 
-  static class RetraceResult {
-
-    private final List<StackTraceNode> nodes;
-
-    RetraceResult(List<StackTraceNode> nodes) {
-      this.nodes = nodes;
-    }
-
-    List<String> toListOfStrings() {
-      List<String> strings = new ArrayList<>(nodes.size());
-      for (StackTraceNode node : nodes) {
-        node.append(strings);
-      }
-      return strings;
-    }
-  }
-
   private final RetraceBase retraceBase;
   private final List<String> stackTrace;
   private final DiagnosticsHandler diagnosticsHandler;
@@ -107,10 +90,14 @@
     this.diagnosticsHandler = diagnosticsHandler;
   }
 
-  public RetraceResult retrace() {
+  public RetraceCommandLineResult retrace() {
     ArrayList<StackTraceNode> result = new ArrayList<>();
     retraceLine(stackTrace, 0, result);
-    return new RetraceResult(result);
+    List<String> retracedStrings = new ArrayList<>();
+    for (StackTraceNode node : result) {
+      node.append(retracedStrings);
+    }
+    return new RetraceCommandLineResult(retracedStrings);
   }
 
   private void retraceLine(List<String> stackTrace, int index, List<StackTraceNode> result) {
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
index b04d9a8..2ab72a7 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
@@ -20,6 +20,7 @@
     this.retraceBase = retraceBase;
   }
 
+  @Override
   public Stream<Element> stream() {
     // Handle void and primitive types as single element results.
     if (obfuscatedType == null || obfuscatedType.isPrimitive()) {
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 69e1930..347ae0f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -29,7 +29,6 @@
 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.ir.optimize.NestUtils;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableList;
@@ -630,6 +629,28 @@
     return constClassReferences.contains(type);
   }
 
+  public AppInfoWithLiveness withStaticFieldWrites(
+      Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts) {
+    assert checkIfObsolete();
+    if (writesWithContexts.isEmpty()) {
+      return this;
+    }
+    AppInfoWithLiveness result = new AppInfoWithLiveness(this);
+    writesWithContexts.forEach(
+        (encodedField, contexts) -> {
+          DexField field = encodedField.field;
+          FieldAccessInfoImpl fieldAccessInfo = result.fieldAccessInfoCollection.get(field);
+          if (fieldAccessInfo == null) {
+            fieldAccessInfo = new FieldAccessInfoImpl(field);
+            result.fieldAccessInfoCollection.extend(field, fieldAccessInfo);
+          }
+          for (DexEncodedMethod context : contexts) {
+            fieldAccessInfo.recordWrite(field, context);
+          }
+        });
+    return result;
+  }
+
   public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
     assert checkIfObsolete();
     if (noLongerWrittenFields.isEmpty()) {
@@ -838,6 +859,19 @@
     return pinnedItems.contains(reference);
   }
 
+  public boolean hasPinnedInstanceInitializer(DexType type) {
+    assert type.isClassType();
+    DexProgramClass clazz = asProgramClassOrNull(definitionFor(type));
+    if (clazz != null) {
+      for (DexEncodedMethod method : clazz.directMethods()) {
+        if (method.isInstanceInitializer() && isPinned(method.method)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   private boolean canVirtualMethodBeImplementedInExtraSubclass(
       DexProgramClass clazz, DexMethod method) {
     // For functional interfaces that are instantiated by lambdas, we may not have synthesized all
@@ -853,7 +887,7 @@
     // overrides the kept method.
     if (isPinned(clazz.type)) {
       ResolutionResult resolutionResult = resolveMethod(clazz, method);
-      if (resolutionResult.hasSingleTarget()) {
+      if (resolutionResult.isSingleResolution()) {
         DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
         return !resolutionTarget.isProgramMethod(this)
             || resolutionTarget.isLibraryMethodOverride().isPossiblyTrue()
@@ -982,9 +1016,24 @@
       DexType refinedReceiverType,
       ClassTypeLatticeElement receiverLowerBoundType) {
     assert checkIfObsolete();
-    DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
-    if (directResult != null) {
-      return directResult;
+    // TODO: replace invocationContext by a DexProgramClass typed formal.
+    DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
+    assert invocationClass != null;
+
+    ResolutionResult resolutionResult = resolveMethodOnClass(method.holder, method);
+    if (!resolutionResult.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
+      return null;
+    }
+
+    DexEncodedMethod topTarget = resolutionResult.getSingleTarget();
+    if (topTarget == null) {
+      // A null target represents a valid target without a known defintion, ie, array clone().
+      return null;
+    }
+
+    // If the target is a private method, then the invocation is a direct access to a nest member.
+    if (topTarget.isPrivateMethod()) {
+      return topTarget;
     }
 
     // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
@@ -992,11 +1041,10 @@
     // from the runtime type of the receiver.
     if (receiverLowerBoundType != null) {
       if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
-        ResolutionResult resolutionResult = resolveMethod(method.holder, method, false);
-        if (resolutionResult.hasSingleTarget()
+        if (resolutionResult.isSingleResolution()
             && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
           ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
-          if (refinedResolutionResult.hasSingleTarget()
+          if (refinedResolutionResult.isSingleResolution()
               && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
             return validateSingleVirtualTarget(
                 refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
@@ -1014,14 +1062,13 @@
     assert method != null;
     assert isSubtype(refinedReceiverType, method.holder);
     if (method.holder.isArrayType()) {
-      // For javac output this will only be clone(), but in general the methods from Object can
-      // be invoked with an array type holder.
       return null;
     }
     DexClass holder = definitionFor(method.holder);
-    if (holder == null || holder.isNotProgramClass() || holder.isInterface()) {
+    if (holder == null || holder.isNotProgramClass()) {
       return null;
     }
+    assert !holder.isInterface();
     boolean refinedReceiverIsStrictSubType = refinedReceiverType != method.holder;
     DexProgramClass refinedHolder =
         (refinedReceiverIsStrictSubType ? definitionFor(refinedReceiverType) : holder)
@@ -1036,7 +1083,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.hasSingleTarget() || !topMethod.isValidVirtualTarget(options())) {
+    if (!topMethod.isSingleResolution() || !topMethod.isValidVirtualTarget(options())) {
       method.setSingleVirtualMethodCache(refinedReceiverType, null);
       return null;
     }
@@ -1063,21 +1110,6 @@
     return result;
   }
 
-  private DexEncodedMethod nestAccessLookup(DexMethod method, DexType invocationContext) {
-    if (method.holder == invocationContext || !definitionFor(invocationContext).isInANest()) {
-      return null;
-    }
-    DexEncodedMethod directTarget = lookupDirectTarget(method);
-    assert directTarget == null || directTarget.method.holder == method.holder;
-    if (directTarget != null
-        && directTarget.isPrivateMethod()
-        && NestUtils.sameNest(method.holder, invocationContext, this)) {
-      return directTarget;
-    }
-
-    return null;
-  }
-
   /**
    * Computes which methods overriding <code>method</code> are visible for the subtypes of type.
    *
@@ -1183,10 +1215,9 @@
       DexType refinedReceiverType,
       ClassTypeLatticeElement receiverLowerBoundType) {
     assert checkIfObsolete();
-    DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
-    if (directResult != null) {
-      return directResult;
-    }
+    // Replace DexType invocationContext by DexProgramClass throughout.
+    DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
+    assert invocationClass != null;
 
     // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
     // runtime type information. In this case, the invoke will dispatch to the resolution result
@@ -1194,10 +1225,10 @@
     if (receiverLowerBoundType != null) {
       if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
         ResolutionResult resolutionResult = resolveMethod(method.holder, method, true);
-        if (resolutionResult.hasSingleTarget()
+        if (resolutionResult.isSingleResolution()
             && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
           ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
-          if (refinedResolutionResult.hasSingleTarget()
+          if (refinedResolutionResult.isSingleResolution()
               && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
             return validateSingleVirtualTarget(
                 refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
@@ -1214,13 +1245,24 @@
     if (holder == null || !holder.accessFlags.isInterface()) {
       return null;
     }
-    // First check that there is a target for this invoke-interface to hit. If there is none,
-    // this will fail at runtime.
-    DexEncodedMethod topTarget = resolveMethodOnInterface(holder, method).getSingleTarget();
-    if (topTarget == null || !SingleResolutionResult.isValidVirtualTarget(options(), topTarget)) {
+    // First check that there is a visible and valid target for this invoke-interface to hit.
+    // If there is none, this will fail at runtime.
+    ResolutionResult topResolution = resolveMethodOnInterface(holder, method);
+    if (!topResolution.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
       return null;
     }
 
+    DexEncodedMethod topTarget = topResolution.getSingleTarget();
+    if (topTarget == null) {
+      // An null target represents a valid target with no known defintion, eg, array clone().
+      return null;
+    }
+
+    // If the target is a private method, then the invocation is a direct access to a nest member.
+    if (topTarget.isPrivateMethod()) {
+      return topTarget;
+    }
+
     // If the invoke could target a method in a class that is not visible to R8, then give up.
     if (canVirtualMethodBeImplementedInExtraSubclass(holder, method)) {
       return null;
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 a9f60ec..3548caf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2022,7 +2022,7 @@
     assert interfaceInvoke == holder.isInterface();
     Set<DexEncodedMethod> possibleTargets =
         // TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved.
-        new SingleResolutionResult(resolution.method)
+        new SingleResolutionResult(holder, resolution.holder, resolution.method)
             .lookupVirtualDispatchTargets(interfaceInvoke, appInfo);
     if (possibleTargets == null || possibleTargets.isEmpty()) {
       return;
@@ -2173,25 +2173,26 @@
     // See <a
     // href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial">
     // the JVM spec for invoke-special.
-    DexEncodedMethod resolutionTarget =
-        appInfo.resolveMethod(method.holder, method).getSingleTarget();
-    if (resolutionTarget == null) {
+    SingleResolutionResult resolution =
+        appInfo.resolveMethod(method.holder, method).asSingleResolution();
+    if (resolution == null) {
       brokenSuperInvokes.add(method);
       reportMissingMethod(method);
       return;
     }
 
+    DexEncodedMethod resolutionTarget = resolution.getResolvedMethod();
     if (resolutionTarget.accessFlags.isPrivate() || resolutionTarget.accessFlags.isStatic()) {
       brokenSuperInvokes.add(resolutionTarget.method);
     }
-    DexProgramClass resolutionTargetClass = getProgramClassOrNull(resolutionTarget.method.holder);
+    DexProgramClass resolutionTargetClass = resolution.getResolvedHolder().asProgramClass();
     if (resolutionTargetClass != null) {
       markMethodAsTargeted(
           resolutionTargetClass, resolutionTarget, KeepReason.targetedBySuperFrom(from));
     }
 
     // Now we need to compute the actual target in the context.
-    DexEncodedMethod target = appInfo.lookupSuperTarget(method, from.method.holder);
+    DexEncodedMethod target = resolution.lookupInvokeSuperTarget(from.method.holder, appInfo);
     if (target == null) {
       // The actual implementation in the super class is missing.
       reportMissingMethod(method);
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 fee13db..24b5004 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -533,7 +533,7 @@
       ResolutionResult resolutionResult =
           appView.appInfo().resolveMethod(originalClazz, method.method);
       if (!resolutionResult.isValidVirtualTarget(appView.options())
-          || !resolutionResult.hasSingleTarget()) {
+          || !resolutionResult.isSingleResolution()) {
         return;
       }
       DexEncodedMethod methodToKeep = resolutionResult.getSingleTarget();
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 9f4b3e3..77de8e2 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1244,7 +1244,7 @@
     // Returns the method that shadows the given method, or null if method is not shadowed.
     private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
       ResolutionResult resolutionResult = appInfo.resolveMethod(target, method.method);
-      if (!resolutionResult.hasSingleTarget()) {
+      if (!resolutionResult.isSingleResolution()) {
         // May happen in case of missing classes, or if multiple implementations were found.
         abortMerge = true;
         return null;
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 e815737..40633b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -223,6 +223,7 @@
   public boolean enablePropagationOfConstantsAtCallSites = false;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
+  public boolean enableSourceDebugExtensionRewriter = false;
 
   public int callGraphLikelySpuriousCallEdgeThreshold = 50;
 
@@ -1013,8 +1014,6 @@
     public boolean forceNameReflectionOptimization = false;
     public boolean enableNarrowingChecksInD8 = false;
     public Consumer<IRCode> irModifier = null;
-    // TODO(b/129458850) When fixed, remove this and change all usages to "true".
-    public boolean enableStatefulLambdaCreateInstanceMethod = false;
     public int basicBlockMuncherIterationLimit = NO_LIMIT;
     public boolean dontReportFailingCheckDiscarded = false;
     public boolean deterministicSortingBasedOnDexType = true;
@@ -1033,10 +1032,6 @@
     // deleted. Useful to check that our dump functionality does not cause compilation failure.
     public boolean dumpAll = false;
 
-    public boolean desugarLambdasThroughLensCodeRewriter() {
-      return enableStatefulLambdaCreateInstanceMethod;
-    }
-
     public boolean readInputStackMaps = false;
 
     // Option for testing outlining with interface array arguments, see b/132420510.
diff --git a/src/test/examplesJava9/backport/StreamBackportJava9Main.java b/src/test/examplesJava9/backport/StreamBackportJava9Main.java
new file mode 100644
index 0000000..b8c8f28
--- /dev/null
+++ b/src/test/examplesJava9/backport/StreamBackportJava9Main.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package backport;
+
+import java.util.stream.Stream;
+
+public final class StreamBackportJava9Main {
+
+  public static void main(String[] args) {
+    testOfNullable();
+  }
+
+  public static void testOfNullable() {
+    Object guineaPig = new Object();
+    Stream<Object> streamNonEmpty = Stream.ofNullable(guineaPig);
+    assertTrue(streamNonEmpty.count() == 1);
+    Stream<Object> streamEmpty = Stream.ofNullable(null);
+    assertTrue(streamEmpty.count() == 0);
+  }
+
+  private static void assertTrue(boolean value) {
+    if (!value) {
+      throw new AssertionError("Expected <true> but was <false>");
+    }
+  }
+}
diff --git a/src/test/examplesJava9/desugaredlib/SynchronizedCollectionMain.java b/src/test/examplesJava9/desugaredlib/SynchronizedCollectionMain.java
new file mode 100644
index 0000000..d600c63
--- /dev/null
+++ b/src/test/examplesJava9/desugaredlib/SynchronizedCollectionMain.java
@@ -0,0 +1,90 @@
+// 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 desugaredlib;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+public class SynchronizedCollectionMain {
+
+  public static void main(String[] args) {
+    // Test 3 maps.
+    // Test keySet entrySet values in syncMap
+    // test navigableKeySet in syncNavigMap
+    // Test rewriting of removeIf, replaceAll, sort.
+    // Retarget removeIf.
+    ArrayList<Integer> list = new ArrayList<>();
+    list.add(1);
+    list.add(2);
+    Collection<Integer> syncCol = Collections.synchronizedCollection(list);
+    syncCol.removeIf(x -> x == 2);
+    System.out.println(syncCol);
+
+    // Retarget sort, replaceAll.
+    ArrayList<Integer> list2 = new ArrayList<>();
+    list2.add(2);
+    list2.add(1);
+    List<Integer> syncList = Collections.synchronizedList(list2);
+    syncList.replaceAll(x -> x + 1);
+    System.out.println(syncList.size());
+    syncList.sort(Comparator.naturalOrder());
+    System.out.println(syncList);
+
+    // Retarget synchronizedMap.
+    Map<Integer, Double> map = new IdentityHashMap<>();
+    map.put(1, 1.1);
+    map.put(2, 2.2);
+    Map<Integer, Double> synchronizedMap = Collections.synchronizedMap(map);
+    // Reflective instantiation.
+    System.out.println(synchronizedMap.keySet().contains(1));
+    System.out.println(synchronizedMap.entrySet().size());
+    System.out.println(synchronizedMap.values().size());
+
+    // Retarget synchronizedSortedMap
+    SortedMap<Integer, Double> sortedMap = new ConcurrentSkipListMap<>();
+    sortedMap.put(1, 1.1);
+    sortedMap.put(2, 2.2);
+    SortedMap<Integer, Double> synchronizedSortedMap = Collections.synchronizedSortedMap(sortedMap);
+    System.out.println(synchronizedSortedMap.size());
+
+    testSynchronization();
+  }
+
+  public static void testSynchronization() {
+    int LIST_SIZE = 10000;
+    // Different thread mutate the same collection. Without synchronization,
+    // some of the integers should be concurrently incremented leading to an invalid result.
+    for (int numThreads : new int[] {4, 5, 8, 9, 15, 16}) {
+      ArrayList<Integer> list = new ArrayList<>();
+      for (int i = 0; i < LIST_SIZE; i++) {
+        list.add(i);
+      }
+      List<Integer> syncList = Collections.synchronizedList(list);
+      try {
+        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
+        for (int i = 0; i < numThreads; i++) {
+          executor.submit(() -> syncList.replaceAll(x -> x + 1));
+        }
+        executor.shutdown();
+        executor.awaitTermination(5, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        throw new RuntimeException(e);
+      }
+      for (int i = 0; i < LIST_SIZE; i++) {
+        assert i + numThreads == list.get(i);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/Collectors.java b/src/test/java/com/android/tools/r8/Collectors.java
new file mode 100644
index 0000000..0e05ce0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/Collectors.java
@@ -0,0 +1,19 @@
+// 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 java.util.stream.Collector;
+
+public abstract class Collectors {
+
+  public static <T> Collector<T, ?, T> toSingle() {
+    return java.util.stream.Collectors.collectingAndThen(
+        java.util.stream.Collectors.toList(),
+        items -> {
+          assert items.size() == 1;
+          return items.get(0);
+        });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTestJdkLib.java b/src/test/java/com/android/tools/r8/D8CommandTestJdkLib.java
new file mode 100644
index 0000000..553452e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8CommandTestJdkLib.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2017, 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+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 D8CommandTestJdkLib extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public D8CommandTestJdkLib(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void jdkLib() throws Throwable {
+    CfRuntime runtime = parameters.getRuntime().asCf();
+    D8Command command = parse("--lib", runtime.getJavaHome().toString());
+    AndroidApp inputApp = ToolHelper.getApp(command);
+    assertEquals(1, inputApp.getLibraryResourceProviders().size());
+    if (runtime.isNewerThan(CfVm.JDK8)) {
+      assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof JdkClassFileProvider);
+    } else {
+      assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof ArchiveClassFileProvider);
+    }
+  }
+
+  private D8Command parse(String... args) throws CompilationFailedException {
+    return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index 6ccf086..1bf86e3 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8;
 
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.OffOrAuto;
@@ -346,20 +346,23 @@
     // implementation from B.
     // test is compiled with incomplete classpath: lib3 is missing so
     // ImplementMethodsWithDefault is missing its super class.
+
     D8TestRunner test =
-        test("desugaringwithmissingclasstest2", "desugaringwithmissingclasstest2", "N/A")
-            .withInterfaceMethodDesugaring(OffOrAuto.Auto)
-            .withClasspath(lib1.getInputJar())
-            .withClasspath(lib2.getInputJar())
-            .withMinApiLevel(minApi);
-    try {
-      test.build();
-      Assert.fail("Expected build to fail with CompilationFailedException");
-    } catch (CompilationFailedException e) {
-      String message = e.getCause().getMessage();
-      assertTrue(message.contains("desugaringwithmissingclasstest2.ImplementMethodsWithDefault"));
-      assertTrue(message.contains("desugaringwithmissingclasslib3.C"));
-    }
+        test("desugaringwithmissingclasstest2", "desugaringwithmissingclasstest2", "N/A");
+
+    TestBase.testForD8(temp)
+        .addProgramFiles(test.getInputJar())
+        .addClasspathFiles(lib1.getInputJar())
+        .addClasspathFiles(lib2.getInputJar())
+        .setMinApi(minApi)
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertOnlyWarnings();
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("desugaringwithmissingclasstest2.ImplementMethodsWithDefault"));
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("desugaringwithmissingclasslib3.C"));
+            });
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 6eba851..049cdb7 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -7,7 +7,6 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.position.Position;
@@ -17,6 +16,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 
 // Helper to check that a particular error occurred.
 public class DiagnosticsChecker implements DiagnosticsHandler {
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index 87f946c..a470bb3 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -4,8 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -17,7 +16,9 @@
           + "java.lang.Object, java.lang.String)";
   protected static final String throwParameterIsNotNullExceptionSignature =
       "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)";
-  protected static final String METADATA_DESCRIPTOR = "Lkotlin/Metadata;";
+  public static final String METADATA_DESCRIPTOR = "Lkotlin/Metadata;";
+  public static final String METADATA_TYPE =
+      DescriptorUtils.descriptorToJavaType(METADATA_DESCRIPTOR);
 
   private static final String RSRC = "kotlinR8TestResources";
 
@@ -48,13 +49,4 @@
   protected Path getMappingfile(String folder, String mappingFileName) {
     return Paths.get(ToolHelper.TESTS_DIR, RSRC, folder, mappingFileName);
   }
-
-  protected DexAnnotation retrieveMetadata(DexClass dexClass) {
-    for (DexAnnotation annotation : dexClass.annotations.annotations) {
-      if (annotation.annotation.type.toDescriptorString().equals(METADATA_DESCRIPTOR)) {
-        return annotation;
-      }
-    }
-    return null;
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTestJdkLib.java b/src/test/java/com/android/tools/r8/R8CommandTestJdkLib.java
new file mode 100644
index 0000000..f5cd550
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8CommandTestJdkLib.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2017, 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+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 R8CommandTestJdkLib extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public R8CommandTestJdkLib(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void jdkLib() throws Throwable {
+    CfRuntime runtime = parameters.getRuntime().asCf();
+    R8Command command = parse("--lib", runtime.getJavaHome().toString());
+    AndroidApp inputApp = ToolHelper.getApp(command);
+    assertEquals(1, inputApp.getLibraryResourceProviders().size());
+    if (runtime.isNewerThan(CfVm.JDK8)) {
+      assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof JdkClassFileProvider);
+    } else {
+      assertTrue(inputApp.getLibraryResourceProviders().get(0) instanceof ArchiveClassFileProvider);
+    }
+  }
+
+  private R8Command parse(String... args) throws CompilationFailedException {
+    return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 5f328d3..8154dfc 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 180, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 137, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -110,10 +110,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        // TODO(b/120814598): Should be 24. Some lambdas are not class inlined because parameter
-        // usages for lambda methods are not present for the class inliner.
-        // TODO(b/141719453): Also, some are not inined due to instruction limits.
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 39, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
         .run();
   }
 
@@ -137,37 +134,6 @@
         .run();
   }
 
-  @Override
-  @Test
-  public void lambdaDesugaringCreateMethod() throws Throwable {
-    test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
-        .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
-        .withOptionConsumer(
-            opts -> {
-              opts.enableClassInlining = false;
-              opts.testing.enableStatefulLambdaCreateInstanceMethod = true;
-            })
-        .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 180, "lambdadesugaring"))
-        .run();
-
-    test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
-        .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
-        .withOptionConsumer(
-            opts -> {
-              opts.enableClassInlining = true;
-              opts.testing.enableStatefulLambdaCreateInstanceMethod = true;
-            })
-        .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        // TODO(b/120814598): Should be 24. Some lambdas are not class inlined because parameter
-        // usages for lambda methods are not present for the class inliner.
-        // TODO(b/141719453): Also, some are not inined due to instruction limits.
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 39, "lambdadesugaring"))
-        .run();
-  }
-
   @Test
   @IgnoreIfVmOlderThan(Version.V7_0_0)
   public void lambdaDesugaringWithDefaultMethods() throws Throwable {
@@ -176,7 +142,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 180, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 137, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -184,10 +150,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        // TODO(b/120814598): Should be 24. Some lambdas are not class inlined because parameter
-        // usages for lambda methods are not present for the class inliner.
-        // TODO(b/141719453): Also, some are not inined due to instruction limits.
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 39, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
         .run();
   }
 
@@ -201,7 +164,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 31, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -211,10 +174,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        // TODO(b/120814598): Should be 5. Some lambdas are not class inlined because parameter
-        // usages for lambda methods are not present for the class inliner.
-        // TODO(b/141719453): Also, some are not inined due to instruction limits.
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 24, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
         .run();
   }
 
@@ -228,7 +188,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 31, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -238,10 +198,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        // TODO(b/120814598): Should be 5. Some lambdas are not class inlined because parameter
-        // usages for lambda methods are not present for the class inliner.
-        // TODO(b/141719453): Also, some are not inined due to instruction limits.
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 24, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 415abb1..50eb3be 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -8,15 +8,12 @@
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.retrace.Retrace;
-import com.android.tools.r8.retrace.RetraceCommand;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import java.io.IOException;
-import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
@@ -73,27 +70,20 @@
     return graphInspector.get();
   }
 
-  public R8TestRunResult  inspectGraph(Consumer<GraphInspector> consumer)
+  public R8TestRunResult inspectGraph(Consumer<GraphInspector> consumer)
       throws IOException, ExecutionException {
     consumer.accept(graphInspector());
     return self();
   }
 
-  public String proguardMap() {
-    return proguardMap;
+  public <E extends Throwable> R8TestRunResult inspectStackTrace(
+      ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer)
+      throws E, IOException, ExecutionException {
+    consumer.accept(getStackTrace(), new CodeInspector(app, proguardMap));
+    return self();
   }
 
-  public List<String> retrace() {
-    class Box {
-      List<String> result;
-    }
-    Box box = new Box();
-    Retrace.run(
-        RetraceCommand.builder()
-            .setProguardMapProducer(() -> proguardMap)
-            .setStackTrace(StringUtils.splitLines(getStdErr()))
-            .setRetracedStackTraceConsumer(retraced -> box.result = retraced)
-            .build());
-    return box.result;
+  public String proguardMap() {
+    return proguardMap;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index a6522b1..daab81d 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -369,15 +369,6 @@
   }
 
   @Test
-  public void lambdaDesugaringCreateMethod() throws Throwable {
-    test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
-        .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
-        .withKeepAll()
-        .withOptionConsumer(o -> o.testing.enableStatefulLambdaCreateInstanceMethod = true)
-        .run();
-  }
-
-  @Test
   public void lambdaDesugaringNPlus() throws Throwable {
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 57eae6b..00d991a 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
@@ -34,6 +35,7 @@
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
@@ -75,6 +77,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.nio.charset.StandardCharsets;
@@ -208,6 +211,19 @@
     return JavaCompilerTool.create(jdk, temp);
   }
 
+  public static KotlinCompilerTool kotlinc(
+      CfRuntime jdk,
+      TemporaryFolder temp,
+      KotlinCompiler kotlinCompiler,
+      KotlinTargetVersion kotlinTargetVersion) {
+    return KotlinCompilerTool.create(jdk, temp, kotlinCompiler, kotlinTargetVersion);
+  }
+
+  public static KotlinCompilerTool kotlinc(
+      KotlinCompiler kotlinCompiler, KotlinTargetVersion kotlinTargetVersion) {
+    return kotlinc(TestRuntime.getCheckedInJdk9(), staticTemp, kotlinCompiler, kotlinTargetVersion);
+  }
+
   public KotlinCompilerTool kotlinc(
       CfRuntime jdk, KotlinCompiler kotlinCompiler, KotlinTargetVersion kotlinTargetVersion) {
     return KotlinCompilerTool.create(jdk, temp, kotlinCompiler, kotlinTargetVersion);
@@ -588,6 +604,17 @@
     return factory.createType(type.getDescriptor());
   }
 
+  protected static DexField buildField(Field field, DexItemFactory factory) {
+    return buildField(Reference.fieldFromField(field), factory);
+  }
+
+  protected static DexField buildField(FieldReference field, DexItemFactory factory) {
+    return factory.createField(
+        buildType(field.getHolderClass(), factory),
+        buildType(field.getFieldType(), factory),
+        field.getFieldName());
+  }
+
   protected static DexMethod buildMethod(Method method, DexItemFactory factory) {
     return buildMethod(Reference.methodFromMethod(method), factory);
   }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 249e39e..c188c87 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -6,7 +6,6 @@
 
 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.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -27,7 +26,9 @@
 
   @Parameters(name = "{1}, minification: {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withDexRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters().withDexRuntimes().withAllRuntimesAndApiLevels().build());
   }
 
   public KeepNonVisibilityBridgeMethodsTest(boolean minification, TestParameters parameters) {
@@ -50,12 +51,13 @@
             "  synthetic void registerObserver(...);",
             "}")
         .allowAccessModification()
+        .enableClassInliningAnnotations()
         // TODO(b/120764902): MemberSubject.getOriginalName() is not working without the @NeverMerge
         //  annotation on DataAdapter.Observer.
         .enableMergeAnnotations()
         .enableProguardTestOptions()
         .minification(minification)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             inspector -> {
@@ -63,7 +65,7 @@
               assertThat(classSubject, isPresent());
 
               MethodSubject subject = classSubject.uniqueMethodWithName("registerObserver");
-              assertTrue(subject.isPresent());
+              assertThat(subject, isPresent());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
index 148a61e..cc60012 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.bridgeremoval.bridgestokeep;
 
-public class SimpleDataAdapter
-    extends SimpleObservableList<DataAdapter.Observer>
+import com.android.tools.r8.NeverClassInline;
+
+@NeverClassInline
+public class SimpleDataAdapter extends SimpleObservableList<DataAdapter.Observer>
     implements DataAdapter {
 
   public SimpleDataAdapter() {
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 82385a2..fc3f22f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -338,11 +338,15 @@
             "classmerging.LambdaRewritingTest",
             "classmerging.LambdaRewritingTest$Function",
             "classmerging.LambdaRewritingTest$InterfaceImpl");
-    runTest(
+    runTestOnInput(
         main,
-        programFiles,
+        readProgramFiles(programFiles),
         name -> preservedClassNames.contains(name) || name.contains("$Lambda$"),
-        getProguardConfig(JAVA8_EXAMPLE_KEEP));
+        getProguardConfig(JAVA8_EXAMPLE_KEEP),
+        options -> {
+          this.configure(options);
+          options.enableClassInlining = false;
+        });
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java
new file mode 100644
index 0000000..0f08ff9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java
@@ -0,0 +1,42 @@
+// 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.backports;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class StreamBackportJava9Test extends AbstractBackportTest {
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+        .build();
+  }
+
+  private static final Path TEST_JAR =
+      Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+
+  public StreamBackportJava9Test(TestParameters parameters) {
+    super(parameters, Stream.class, TEST_JAR, "backport.StreamBackportJava9Main");
+    // Available since N as part of library desugaring.
+    ignoreInvokes("of");
+    ignoreInvokes("empty");
+    ignoreInvokes("count");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
new file mode 100644
index 0000000..edaf321
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
@@ -0,0 +1,254 @@
+// 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.desugaredlibrary;
+
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Spliterator;
+import org.jetbrains.annotations.NotNull;
+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 CustomCollectionForwardingTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public CustomCollectionForwardingTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCustomCollectionD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(CustomCollectionForwardingTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspect(this::assertForwardingMethods)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(
+            StringUtils.lines("false", "false", "false", "false", "false", "false"));
+  }
+
+  private void assertForwardingMethods(CodeInspector inspector) {
+    if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
+      return;
+    }
+    ClassSubject cal = inspector.clazz(CustomArrayList.class);
+    MethodSubject spliteratorCal = cal.method("j$.util.Spliterator", "spliterator");
+    assertTrue(spliteratorCal.isPresent());
+    assertTrue(
+        spliteratorCal
+            .streamInstructions()
+            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
+    MethodSubject streamCal = cal.method("j$.util.stream.Stream", "stream");
+    assertTrue(streamCal.isPresent());
+    assertTrue(
+        streamCal
+            .streamInstructions()
+            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
+
+    ClassSubject clhs = inspector.clazz(CustomLinkedHashSet.class);
+    MethodSubject spliteratorClhs = clhs.method("j$.util.Spliterator", "spliterator");
+    assertTrue(spliteratorClhs.isPresent());
+    assertTrue(
+        spliteratorClhs
+            .streamInstructions()
+            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("DesugarLinkedHashSet")));
+    MethodSubject streamClhs = clhs.method("j$.util.stream.Stream", "stream");
+    assertTrue(streamClhs.isPresent());
+    assertTrue(
+        streamClhs
+            .streamInstructions()
+            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
+
+    ClassSubject cl = inspector.clazz(CustomList.class);
+    MethodSubject spliteratorCl = cl.method("j$.util.Spliterator", "spliterator");
+    assertTrue(spliteratorCl.isPresent());
+    assertTrue(
+        spliteratorCl
+            .streamInstructions()
+            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
+    MethodSubject streamCl = cl.method("j$.util.stream.Stream", "stream");
+    assertTrue(streamCl.isPresent());
+    assertTrue(
+        streamCl
+            .streamInstructions()
+            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      CustomArrayList<Object> objects = new CustomArrayList<>();
+      System.out.println(objects.spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+      System.out.println(objects.stream().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+
+      CustomLinkedHashSet<Object> objects2 = new CustomLinkedHashSet<>();
+      System.out.println(objects2.spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+      System.out.println(
+          objects2.stream().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+
+      CustomList<Object> objects3 = new CustomList<>();
+      System.out.println(objects3.spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+      System.out.println(
+          objects3.stream().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
+    }
+  }
+
+  static class CustomArrayList<E> extends ArrayList<E> implements Collection<E> {}
+
+  static class CustomLinkedHashSet<E> extends LinkedHashSet<E> implements Collection<E> {}
+
+  static class CustomList<E> implements Collection<E>, List<E> {
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] a) {
+      return null;
+    }
+
+    @Override
+    public boolean add(E e) {
+      return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends E> c) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(int index, @NotNull Collection<? extends E> c) {
+      return false;
+    }
+
+    @Override
+    public boolean removeAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+
+    @Override
+    public E get(int index) {
+      return null;
+    }
+
+    @Override
+    public E set(int index, E element) {
+      return null;
+    }
+
+    @Override
+    public void add(int index, E element) {}
+
+    @Override
+    public E remove(int index) {
+      return null;
+    }
+
+    @Override
+    public int indexOf(Object o) {
+      return 0;
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+      return 0;
+    }
+
+    @NotNull
+    @Override
+    public ListIterator<E> listIterator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public ListIterator<E> listIterator(int index) {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public List<E> subList(int fromIndex, int toIndex) {
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
new file mode 100644
index 0000000..d00811f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
@@ -0,0 +1,328 @@
+// 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.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Predicate;
+import org.jetbrains.annotations.NotNull;
+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 CustomCollectionInterfaceSuperTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimes().withAllApiLevels().build());
+  }
+
+  public CustomCollectionInterfaceSuperTest(
+      boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines(
+          "removeIf from MyCol1",
+          "removeIf from MyCol1",
+          "removeIf from MyCol2",
+          "removeIf from MyCol1",
+          "removeIf from MyCol2",
+          "removeIf from MyCol1");
+
+  @Test
+  public void testCustomCollectionD8() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addInnerClasses(CustomCollectionInterfaceSuperTest.class)
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+      return;
+    }
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(CustomCollectionInterfaceSuperTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .assertNoMessages()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class Main {
+
+    @SuppressWarnings({
+      "MismatchedQueryAndUpdateOfCollection",
+      "RedundantOperationOnEmptyContainer"
+    })
+    public static void main(String[] args) {
+      Col<Integer> ints = new Col<>();
+      Col1<Integer> ints1 = new Col1<>();
+      Col2<Integer> ints2 = new Col2<>();
+      ints.removeIf(x -> x == 1);
+      ints.superRemoveIf(x -> x == 2);
+      ints1.removeIf(x -> x == 3);
+      ints1.superRemoveIf(x -> x == 4);
+      ints2.removeIf(x -> x == 5);
+      ints2.superRemoveIf(x -> x == 6);
+    }
+  }
+
+  interface MyCol1<E> extends Collection<E> {
+
+    @Override
+    default boolean removeIf(Predicate<? super E> filter) {
+      System.out.println("removeIf from MyCol1");
+      return Collection.super.removeIf(filter);
+    }
+  }
+
+  interface MyCol2<E> extends MyCol1<E> {
+
+    @Override
+    default boolean removeIf(Predicate<? super E> filter) {
+      System.out.println("removeIf from MyCol2");
+      return MyCol1.super.removeIf(filter);
+    }
+  }
+
+  static class Col<E> implements Collection<E> {
+
+    public boolean superRemoveIf(Predicate<? super E> filter) {
+      return Collection.super.removeIf(filter);
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return Collections.emptyIterator();
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] a) {
+      return a;
+    }
+
+    @Override
+    public boolean add(E e) {
+      return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends E> c) {
+      return false;
+    }
+
+    @Override
+    public boolean removeAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+  }
+
+  static class Col1<E> implements MyCol1<E> {
+
+    public boolean superRemoveIf(Predicate<? super E> filter) {
+      return MyCol1.super.removeIf(filter);
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return Collections.emptyIterator();
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] a) {
+      return a;
+    }
+
+    @Override
+    public boolean add(E e) {
+      return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends E> c) {
+      return false;
+    }
+
+    @Override
+    public boolean removeAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+  }
+
+  static class Col2<E> implements MyCol2<E> {
+
+    public boolean superRemoveIf(Predicate<? super E> filter) {
+      return MyCol2.super.removeIf(filter);
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return Collections.emptyIterator();
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] a) {
+      return a;
+    }
+
+    @Override
+    public boolean add(E e) {
+      return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends E> c) {
+      return false;
+    }
+
+    @Override
+    public boolean removeAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+  }
+}
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 6085be7..cd3e666 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
@@ -59,6 +59,7 @@
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
+            .assertNoMessages()
             .inspect(
                 inspector -> {
                   this.assertCustomCollectionCallsCorrect(inspector, false);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
index 220147f..b7181de 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
@@ -81,7 +81,6 @@
           .setMinApi(parameters.getApiLevel())
           .enableCoreLibraryDesugaring(parameters.getApiLevel())
           .compile()
-          .disassemble()
           .addDesugaredCoreLibraryRunClassPath(
               this::buildDesugaredLibrary, parameters.getApiLevel())
           .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
index 7716c96..ec4ba98 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 
@@ -78,22 +77,9 @@
   }
 
   private void checkResult(D8TestRunResult result) {
-    // TODO(b/145506767): Default method desugaring fails to generate a library forwarding method.
-    if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
-      result.assertFailureWithErrorThatMatches(
-          containsString(
-              parameters
-                      .getRuntime()
-                      .asDex()
-                      .getVm()
-                      .getVersion()
-                      .isOlderThanOrEqual(Version.V4_4_4)
-                  ? "VerifyError"
-                  : AbstractMethodError.class.getName()));
-      return;
-    }
-    // TODO(b/145504401): Execution on Art 7.0.0 has the wrong runtime behavior.
+    // TODO(b/145504401): Execution on Art 7.0.0 has the wrong runtime behavior (non-desugared).
     if (parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
         && parameters.getRuntime().asDex().getVm().getVersion().equals(Version.V7_0_0)) {
       result.assertSuccessWithOutputLines("42", "42");
       return;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 17b2a61..61296c9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -110,7 +110,10 @@
     String[] lines = stdOut.split("\n");
     assert lines.length % 2 == 0;
     for (int i = 0; i < lines.length; i += 2) {
-      assertEquals(lines[i], lines[i + 1]);
+      assertEquals(
+          "Different lines: " + lines[i] + " || " + lines[i + 1] + "\n" + stdOut,
+          lines[i],
+          lines[i + 1]);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java
index 5851353..451a255 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java
@@ -58,7 +58,7 @@
 
   private void assertEmulateInterfaceClassesPresentWithDispatchMethods(CodeInspector inspector) {
     List<FoundClassSubject> emulatedInterfaces = getEmulatedInterfaces(inspector);
-    int numDispatchClasses = 8;
+    int numDispatchClasses = 9;
     assertThat(inspector.clazz("j$.util.Map$Entry$-EL"), not(isPresent()));
     assertEquals(numDispatchClasses, emulatedInterfaces.size());
     for (FoundClassSubject clazz : emulatedInterfaces) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
new file mode 100644
index 0000000..f6e0d0a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
@@ -0,0 +1,89 @@
+// 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.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.jetbrains.annotations.NotNull;
+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 IterableTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("1", "2", "3", "4", "5", "Count: 4");
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimes().withAllApiLevels().build());
+  }
+
+  public IterableTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testIterable() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addInnerClasses(IterableTest.class)
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+      return;
+    }
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(IterableTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Iterable<Integer> iterable = new MyIterable<>(Arrays.asList(1, 2, 3, 4, 5));
+      iterable.forEach(System.out::println);
+      Stream<Integer> stream = StreamSupport.stream(iterable.spliterator(), false);
+      System.out.println("Count: " + stream.filter(x -> x != 3).count());
+    }
+  }
+
+  static class MyIterable<E> implements Iterable<E> {
+
+    private Collection<E> collection;
+
+    public MyIterable(Collection<E> collection) {
+      this.collection = collection;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return collection.iterator();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index eebea28..04a7258 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -5,11 +5,12 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import java.time.Instant;
 import java.time.ZonedDateTime;
 import java.util.Date;
 import java.util.GregorianCalendar;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.IntUnaryOperator;
 import org.junit.Test;
@@ -21,26 +22,33 @@
 public class RetargetOverrideTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
   }
 
-  public RetargetOverrideTest(TestParameters parameters) {
+  public RetargetOverrideTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
 
   @Test
   public void testRetargetOverrideD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdout =
         testForD8()
             .addInnerClasses(RetargetOverrideTest.class)
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .setMinApi(parameters.getApiLevel())
             .compile()
             .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibrary, parameters.getApiLevel())
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
             .run(parameters.getRuntime(), Executor.class)
             .assertSuccess()
             .getStdOut();
@@ -49,15 +57,19 @@
 
   @Test
   public void testRetargetOverrideR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdout =
         testForR8(Backend.DEX)
             .addKeepMainRule(Executor.class)
             .addInnerClasses(RetargetOverrideTest.class)
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .setMinApi(parameters.getApiLevel())
             .compile()
             .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibrary, parameters.getApiLevel())
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
             .run(parameters.getRuntime(), Executor.class)
             .assertSuccess()
             .getStdOut();
@@ -67,22 +79,18 @@
   static class Executor {
 
     public static void main(String[] args) {
-      java.sql.Date date = new java.sql.Date(123456789);
-      // The following one is not working on JVMs, but works on Android...
-      System.out.println(date.toInstant());
-      System.out.println("1970-01-02T10:17:36.789Z");
+      directTypes();
+      polyTypes();
+      baseTypes();
+    }
 
-      GregorianCalendar gregCal = new GregorianCalendar(1990, 2, 22);
-      System.out.println(gregCal.toInstant());
+    public static void directTypes() {
+      MyCalendarOverride myCal = new MyCalendarOverride(1990, 2, 22);
+      System.out.println(myCal.toZonedDateTime());
+      System.out.println("1990-11-22T00:00Z[GMT]");
+      System.out.println(myCal.toInstant());
       System.out.println("1990-03-22T00:00:00Z");
 
-      // TODO(b/142846107): Enable overrides of retarget core members.
-      // MyCalendarOverride myCal = new MyCalendarOverride(1990, 2, 22);
-      // System.out.println(myCal.toZonedDateTime());
-      // System.out.println("1990-11-22T00:00Z[GMT]");
-      // System.out.println(myCal.toInstant());
-      // System.out.println("1990-03-22T00:00:00Z");
-
       MyCalendarNoOverride myCalN = new MyCalendarNoOverride(1990, 2, 22);
       System.out.println(myCalN.toZonedDateTime());
       System.out.println("1990-03-22T00:00Z[GMT]");
@@ -93,10 +101,13 @@
       System.out.println(myCalN.superToInstant());
       System.out.println("1990-03-22T00:00:00Z");
 
-      // TODO(b/142846107): Enable overrides of retarget core members.
-      // MyDateOverride myDate = new MyDateOverride(123456789);
-      // System.out.println(myDate.toInstant());
-      // System.out.println("1970-01-02T10:17:45.789Z");
+      MyDateDoubleOverride myDateCast2 = new MyDateDoubleOverride(123456789);
+      System.out.println(myDateCast2.toInstant());
+      System.out.println("1970-01-02T10:17:48.789Z");
+
+      MyDateOverride myDate = new MyDateOverride(123456789);
+      System.out.println(myDate.toInstant());
+      System.out.println("1970-01-02T10:17:45.789Z");
 
       MyDateNoOverride myDateN = new MyDateNoOverride(123456789);
       System.out.println(myDateN.toInstant());
@@ -112,21 +123,58 @@
       System.out.println(myAtomicInteger.updateAndGet(x -> x + 100));
       System.out.println("145");
     }
+
+    public static void polyTypes() {
+      Date myDateCast = new MyDateOverride(123456789);
+      System.out.println(myDateCast.toInstant());
+      System.out.println("1970-01-02T10:17:45.789Z");
+
+      Date myDateCast2 = new MyDateDoubleOverride(123456789);
+      System.out.println(myDateCast2.toInstant());
+      System.out.println("1970-01-02T10:17:48.789Z");
+
+      Date myDateN = new MyDateNoOverride(123456789);
+      System.out.println(myDateN.toInstant());
+      System.out.println("1970-01-02T10:17:36.789Z");
+
+      GregorianCalendar myCalCast = new MyCalendarOverride(1990, 2, 22);
+      System.out.println(myCalCast.toZonedDateTime());
+      System.out.println("1990-11-22T00:00Z[GMT]");
+      System.out.println(myCalCast.toInstant());
+      System.out.println("1990-03-22T00:00:00Z");
+
+      GregorianCalendar myCalN = new MyCalendarNoOverride(1990, 2, 22);
+      System.out.println(myCalN.toZonedDateTime());
+      System.out.println("1990-03-22T00:00Z[GMT]");
+      System.out.println(myCalN.toInstant());
+      System.out.println("1990-03-22T00:00:00Z");
+    }
+
+    public static void baseTypes() {
+      java.sql.Date date = new java.sql.Date(123456789);
+      // The following one is not working on JVMs, but works on Android...
+      System.out.println(date.toInstant());
+      System.out.println("1970-01-02T10:17:36.789Z");
+
+      GregorianCalendar gregCal = new GregorianCalendar(1990, 2, 22);
+      System.out.println(gregCal.toInstant());
+      System.out.println("1990-03-22T00:00:00Z");
+    }
   }
 
-  // static class MyCalendarOverride extends GregorianCalendar {
-  //
-  //   public MyCalendarOverride(int year, int month, int dayOfMonth) {
-  //     super(year, month, dayOfMonth);
-  //   }
-  //
-  //   // Cannot override toInstant (final).
-  //
-  //   @Override
-  //   public ZonedDateTime toZonedDateTime() {
-  //     return super.toZonedDateTime().withMonth(11);
-  //   }
-  // }
+  static class MyCalendarOverride extends GregorianCalendar {
+
+    public MyCalendarOverride(int year, int month, int dayOfMonth) {
+      super(year, month, dayOfMonth);
+    }
+
+    // Cannot override toInstant (final).
+
+    @Override
+    public ZonedDateTime toZonedDateTime() {
+      return super.toZonedDateTime().withMonth(11);
+    }
+  }
 
   static class MyCalendarNoOverride extends GregorianCalendar {
     public MyCalendarNoOverride(int year, int month, int dayOfMonth) {
@@ -142,17 +190,29 @@
     }
   }
 
-  // static class MyDateOverride extends Date {
-  //
-  //   public MyDateOverride(long date) {
-  //     super(date);
-  //   }
-  //
-  //   @Override
-  //   public Instant toInstant() {
-  //     return super.toInstant().plusSeconds(9);
-  //   }
-  // }
+  static class MyDateOverride extends Date {
+
+    public MyDateOverride(long date) {
+      super(date);
+    }
+
+    @Override
+    public Instant toInstant() {
+      return super.toInstant().plusSeconds(9);
+    }
+  }
+
+  static class MyDateDoubleOverride extends MyDateOverride {
+
+    public MyDateDoubleOverride(long date) {
+      super(date);
+    }
+
+    @Override
+    public Instant toInstant() {
+      return super.toInstant().plusSeconds(3);
+    }
+  }
 
   static class MyDateNoOverride extends Date {
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
new file mode 100644
index 0000000..a9df25b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
@@ -0,0 +1,52 @@
+// 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.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SynchronizedCollectionTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public SynchronizedCollectionTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testExecution() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "desugaredlib.jar"))
+        .addInnerClasses(SynchronizedCollectionTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), "desugaredlib.SynchronizedCollectionMain")
+        .assertSuccessWithOutput(StringUtils.lines("[1]", "2", "[2, 3]", "true", "2", "2", "2"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionLargeWarningTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionLargeWarningTest.java
deleted file mode 100644
index fe0e1a5..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionLargeWarningTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-
-import static org.hamcrest.CoreMatchers.startsWith;
-
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.nio.file.Path;
-import java.time.Clock;
-import java.util.function.Function;
-import java.util.stream.Stream;
-import org.junit.Test;
-
-public class APIConversionLargeWarningTest extends DesugaredLibraryTestBase {
-
-  @Test
-  public void testFinalMethod() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
-    testForD8()
-        .setMinApi(AndroidApiLevel.B)
-        .addProgramClasses(Executor.class)
-        .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-        .compile()
-        .assertInfoMessageThatMatches(
-            startsWith(
-                "Desugared library API conversion: Generating a large wrapper for"
-                    + " java.util.stream.Stream"))
-        .assertNoInfoMessageThatMatches(
-            startsWith(
-                "Desugared library API conversion: Generating a large wrapper for java.time.Clock"))
-        .assertNoInfoMessageThatMatches(
-            startsWith(
-                "Desugared library API conversion: Generating a large wrapper for"
-                    + " java.util.function.Function"));
-  }
-
-  static class Executor {
-
-    public static void main(String[] args) {
-      CustomLibClass.callClock(Clock.systemUTC());
-      CustomLibClass.callStream(Stream.empty());
-      CustomLibClass.callFunction(x -> x);
-    }
-  }
-
-  // This class will be put at compilation time as library and on the runtime class path.
-  // This class is convenient for easy testing. Each method plays the role of methods in the
-  // platform APIs for which argument/return values need conversion.
-  static class CustomLibClass {
-
-    public static void callStream(Stream stream) {}
-
-    public static void callClock(Clock clock) {}
-
-    public static void callFunction(Function<String, String> func) {}
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
index 4c2007f..1cb8f74 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
@@ -60,7 +60,7 @@
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel())
         .compile()
-        .assertOnlyInfos() // No warnings.
+        .assertNoMessages()
         .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutput(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index 433f56a..3f4a1ee 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -78,11 +77,6 @@
         "org/openjdk/tests/java/util/stream/WhileOpStatefulTest.java",
         "org/openjdk/tests/java/util/stream/IterateTest.java",
         "org/openjdk/tests/java/util/stream/WhileOpTest.java",
-        // forEach failure
-        "org/openjdk/tests/java/util/stream/FindFirstOpTest.java",
-        "org/openjdk/tests/java/util/stream/MapOpTest.java",
-        // Disabled because explicit cast done on a wrapped value.
-        // "org/openjdk/tests/java/util/SplittableRandomTest.java",
         // Assertion error
         "org/openjdk/tests/java/util/stream/StreamCloseTest.java",
         "org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java",
@@ -90,8 +84,9 @@
         // J9 Random problem
         "org/openjdk/tests/java/util/stream/LongPrimitiveOpsTests.java",
         "org/openjdk/tests/java/util/stream/IntPrimitiveOpsTests.java",
-        "org/openjdk/tests/java/util/stream/DistinctOpTest.java",
         "org/openjdk/tests/java/util/stream/DoublePrimitiveOpsTests.java"
+        // Disabled because explicit cast done on a wrapped value.
+        // "org/openjdk/tests/java/util/SplittableRandomTest.java",
       };
 
   // Disabled because time to run > 1 min for each test.
@@ -112,6 +107,9 @@
 
   private static String[] SUCCESSFUL_RUNNABLE_TESTS =
       new String[] {
+        "org/openjdk/tests/java/util/stream/FindFirstOpTest.java",
+        "org/openjdk/tests/java/util/stream/MapOpTest.java",
+        "org/openjdk/tests/java/util/stream/DistinctOpTest.java",
         "org/openjdk/tests/java/util/MapTest.java",
         "org/openjdk/tests/java/util/FillableStringTest.java",
         "org/openjdk/tests/java/util/stream/ForEachOpTest.java",
@@ -154,7 +152,6 @@
         "takeWhile(",
         "dropWhile(",
         "iterate(",
-        "ofNullable(",
         "range(",
         "doubles(",
         // Collectors
@@ -233,6 +230,7 @@
           compileResult.run(
               parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
       assertTrue(
+          "Failure in " + path + "\n" + result,
           result
               .getStdOut()
               .endsWith(
@@ -250,36 +248,15 @@
       D8TestRunResult result =
           compileResult.run(
               parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
-      if (result
-          .getStdOut()
-          .endsWith(
-              StringUtils.lines("Tests result in " + runnableTests.get(path) + ": SUCCESS"))) {
-        // The test succeeds, this can happen on tests succeeding only on high API levels.
-        assertTrue(
-            parameters.getRuntime().asDex().getMinApiLevel().getLevel()
-                >= AndroidApiLevel.N.getLevel());
-      } else if (result.getStdOut().contains("java.lang.NoSuchMethodError")
+      if (result.getStdOut().contains("java.lang.NoSuchMethodError")
           && Arrays.stream(missingDesugaredMethods())
               .anyMatch(method -> result.getStdOut().contains(method))) {
         // TODO(b/134732760): support Java 9 APIs.
-      } else if (result
-          .getStdOut()
-          .contains("java.lang.NoSuchMethodError: No interface method forEach")) {
-        // TODO(b/134732760): fix tests no to use Iterable#forEach
       } else if (result.getStdOut().contains("in class Ljava/util/Random")
           && result.getStdOut().contains("java.lang.NoSuchMethodError")) {
         // TODO(b/134732760): Random Java 9 Apis, support or do not use them.
       } else if (result.getStdOut().contains("java.lang.AssertionError")) {
         // TODO(b/134732760): Investigate and fix these issues.
-      } else if (result.getStdOut().contains("java.lang.NoClassDefFoundError")) {
-        // Use of high library API on low API device, cannot do anything about this.
-        if (!shrinkDesugaredLibrary) {
-          assertTrue(
-              result.getStdErr().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX));
-        }
-        assertTrue(
-            parameters.getRuntime().asDex().getMinApiLevel().getLevel()
-                < AndroidApiLevel.N.getLevel());
       } else {
         String errorMessage = "STDOUT:\n" + result.getStdOut() + "STDERR:\n" + result.getStdErr();
         fail(errorMessage);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
index 736acc5..f6a4285 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
@@ -71,14 +71,11 @@
         "test.java.time.format.TestDateTimeFormatter",
         "test.java.time.TestLocalDate",
       };
-  // TODO(b/134732760): Investigate why these tests fail.
-  private static String[] forEachProblem =
+  private static String[] formattingProblem =
       new String[] {
-        // ForEach problem
         "test.java.time.format.TestNarrowMonthNamesAndDayNames",
       };
-  private static String[] successes =
-      new String[] {
+  private static String[] successes = new String[] {
         "test.java.time.TestYearMonth",
         "test.java.time.TestZonedDateTime",
         "test.java.time.TestClock_Tick",
@@ -159,20 +156,19 @@
       } else if (result.getStdErr().contains("no microsecond precision")) {
         // Emulator precision, won't fix.
       } else {
-        assertTrue(result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
+        assertTrue(
+            "Failure in " + success + "\n" + result,
+            result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
       }
     }
-    for (String success : forEachProblem) {
+    for (String issue : formattingProblem) {
       D8TestRunResult result =
-          compileResult.run(parameters.getRuntime(), "TestNGMainRunner", verbosity, success);
+          compileResult.run(parameters.getRuntime(), "TestNGMainRunner", verbosity, issue);
       if (requiresAnyCoreLibDesugaring(parameters)) {
-        assertTrue(
-            result.getStdOut().contains("AssertionError")
-                // TODO(b/143275651) raise the right error.
-                || result.getStdOut().contains("NoClassDefFoundError: $r8$wrapper$java")
-                || result.getStdOut().contains("forEach("));
+        // Fails due to formatting differences in desugared library.
+        assertTrue(result.getStdOut().contains("for style NARROW"));
       } else {
-        assertTrue(result.getStdOut().contains(StringUtils.lines(success + ": SUCCESS")));
+        assertTrue(result.getStdOut().contains(StringUtils.lines(issue + ": SUCCESS")));
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index 91d0e98..cd8ec64 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
@@ -99,17 +100,18 @@
   @Test
   public void testHelloCompiledWithD8Dex() throws Exception {
     Path helloOutput = temp.newFolder("helloOutput").toPath().resolve("out.zip").toAbsolutePath();
-    compileR8ToDexWithD8()
-        .run(
-            parameters.getRuntime(),
-            D8.class,
-            "--release",
-            "--output",
-            helloOutput.toString(),
-            "--lib",
-            commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
-            HELLO_PATH)
-        .assertSuccess();
+    D8TestRunResult run =
+        compileR8ToDexWithD8()
+            .run(
+                parameters.getRuntime(),
+                D8.class,
+                "--release",
+                "--output",
+                helloOutput.toString(),
+                "--lib",
+                commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
+                HELLO_PATH);
+    run.assertSuccess();
     verifyResult(helloOutput);
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
index d448e1e..4b5e452 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
@@ -40,6 +40,15 @@
   }
 
   @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramFiles(classesMatching("NestPvtMethodCallInlined"))
+        .run(parameters.getRuntime(), getMainClass("pvtCallInlined"))
+        .disassemble()
+        .assertSuccessWithOutput(getExpectedResult("pvtCallInlined"));
+  }
+
+  @Test
   public void testPvtMethodCallInlined() throws Exception {
     List<Path> toCompile = classesMatching("NestPvtMethodCallInlined");
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java
new file mode 100644
index 0000000..3317df1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java
@@ -0,0 +1,74 @@
+// 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.desugaring.interfacemethods;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ReturnTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ReturnTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReturn() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(ReturnTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(
+            StringUtils.lines(
+                "com.android.tools.r8.desugaring.interfacemethods.ReturnTest$SuperA",
+                "com.android.tools.r8.desugaring.interfacemethods.ReturnTest$A"));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new SuperA().print();
+      new A().print();
+    }
+  }
+
+  static class SuperA implements SuperI {
+    public void print() {
+      SuperA a = get();
+      System.out.println(a.getClass().getName());
+    }
+  }
+
+  static class A extends SuperA implements I {
+    public void print() {
+      A a = get();
+      System.out.println(a.getClass().getName());
+    }
+  }
+
+  interface I extends SuperI {
+    default A get() {
+      return new A();
+    }
+  }
+
+  interface SuperI {
+    default SuperA get() {
+      return new SuperA();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/TrapeziumTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/TrapeziumTest.java
new file mode 100644
index 0000000..ee0308f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/TrapeziumTest.java
@@ -0,0 +1,61 @@
+// 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.desugaring.interfacemethods;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TrapeziumTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public TrapeziumTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testTrapezium() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(TrapeziumTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(StringUtils.lines("foo from superI", "foo from I"));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new SuperA().foo();
+      new A().foo();
+    }
+  }
+
+  static class SuperA implements SuperI {}
+
+  static class A extends SuperA implements I {}
+
+  interface I extends SuperI {
+    default void foo() {
+      System.out.println("foo from I");
+    }
+  }
+
+  interface SuperI {
+    default void foo() {
+      System.out.println("foo from superI");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index 5bf5a36..f71538c 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -3,48 +3,371 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.ToolHelper;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
-import com.android.tools.r8.graph.invokesuper.Consumer;
-import com.android.tools.r8.graph.invokesuper.InvokerClassDump;
-import com.android.tools.r8.graph.invokesuper.InvokerClassFailingDump;
-import com.android.tools.r8.graph.invokesuper.MainClass;
-import com.android.tools.r8.graph.invokesuper.MainClassFailing;
-import com.android.tools.r8.graph.invokesuper.SubLevel1;
-import com.android.tools.r8.graph.invokesuper.SubLevel2;
-import com.android.tools.r8.graph.invokesuper.SubclassOfInvokerClass;
-import com.android.tools.r8.graph.invokesuper.Super;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
-@RunWith(VmTestRunner.class)
-public class InvokeSuperTest extends AsmTestBase {
+@RunWith(Parameterized.class)
+public class InvokeSuperTest extends TestBase {
 
-  @Test
-  @IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
-  public void testInvokeSuperTargets() throws Exception {
-    ensureSameOutput(MainClass.class.getCanonicalName(),
-        ToolHelper.getClassAsBytes(MainClass.class),
-        ToolHelper.getClassAsBytes(Consumer.class),
-        ToolHelper.getClassAsBytes(Super.class),
-        ToolHelper.getClassAsBytes(SubLevel1.class),
-        ToolHelper.getClassAsBytes(SubLevel2.class),
-        InvokerClassDump.dump(),
-        ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  final TestParameters parameters;
+
+  public InvokeSuperTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  static final String EXPECTED =
+      StringUtils.lines(
+          "superMethod in SubLevel2",
+          "superMethod in SubLevel2",
+          "superMethod in SubLevel2",
+          "java.lang.NoSuchMethodError",
+          "subLevel1Method in SubLevel2",
+          "subLevel1Method in SubLevel2",
+          "subLevel2Method in SubLevel2",
+          "From SubLevel1: otherSuperMethod in Super");
+
+  static final String UNEXPECTED_DEX_5_AND_6_OUTPUT =
+      StringUtils.lines(
+          "superMethod in Super",
+          "superMethod in SubLevel1",
+          "superMethod in SubLevel2",
+          "java.lang.NoSuchMethodError",
+          "subLevel1Method in SubLevel1",
+          "subLevel1Method in SubLevel2",
+          "subLevel2Method in SubLevel2",
+          "From SubLevel1: otherSuperMethod in Super");
+
+  String getExpectedOutput() {
+    if (parameters.isDexRuntime()) {
+      Version version = parameters.getRuntime().asDex().getVm().getVersion();
+      if (version.isAtLeast(Version.V5_1_1) && version.isOlderThanOrEqual(Version.V6_0_1)) {
+        return UNEXPECTED_DEX_5_AND_6_OUTPUT;
+      }
+    }
+    return EXPECTED;
   }
 
   @Test
-  public void testInvokeSuperTargetsNonVerifying() throws Exception {
-    ensureR8FailsWithCompilationError(MainClassFailing.class.getCanonicalName(),
-        ToolHelper.getClassAsBytes(MainClassFailing.class),
-        ToolHelper.getClassAsBytes(Consumer.class),
-        ToolHelper.getClassAsBytes(Super.class),
-        ToolHelper.getClassAsBytes(SubLevel1.class),
-        ToolHelper.getClassAsBytes(SubLevel2.class),
-        InvokerClassFailingDump.dump(),
-        ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(
+            MainClass.class,
+            Consumer.class,
+            Super.class,
+            SubLevel1.class,
+            SubLevel2.class,
+            SubClassOfInvokerClass.class)
+        .addProgramClassFileData(InvokerClassDump.dumpVerifying())
+        .run(parameters.getRuntime(), MainClass.class)
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            MainClass.class,
+            Consumer.class,
+            Super.class,
+            SubLevel1.class,
+            SubLevel2.class,
+            SubClassOfInvokerClass.class)
+        .addProgramClassFileData(InvokerClassDump.dumpVerifying())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MainClass.class)
+        .run(parameters.getRuntime(), MainClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testReferenceNonVerifying() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(
+            MainClassFailing.class,
+            Consumer.class,
+            Super.class,
+            SubLevel1.class,
+            SubLevel2.class,
+            SubClassOfInvokerClass.class)
+        .addProgramClassFileData(InvokerClassDump.dumpNonVerifying())
+        .run(parameters.getRuntime(), MainClassFailing.class)
+        .apply(this::checkNonVerifyingResult);
+  }
+
+  private void checkNonVerifyingResult(TestRunResult<?> result) {
+    // The input is invalid and any JVM will fail at verification time.
+    if (parameters.isCfRuntime()) {
+      result.assertFailureWithErrorThatMatches(containsString(VerifyError.class.getName()));
+      return;
+    }
+    // D8 cannot verify its inputs and the behavior of the compiled output differs.
+    // The failure is due to lambda desugaring on pre-7 and fails at runtime on 7+.
+    Version version = parameters.getRuntime().asDex().getVm().getVersion();
+    if (version.isOlderThanOrEqual(Version.V6_0_1)) {
+      result.assertFailureWithErrorThatMatches(
+          allOf(containsString("java.lang.NoClassDefFoundError"), containsString("-$$Lambda$")));
+      return;
+    }
+    result.assertSuccessWithOutputLines(NoSuchMethodError.class.getName());
+  }
+
+  @Test
+  public void testR8NonVerifying() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(
+              MainClassFailing.class,
+              Consumer.class,
+              Super.class,
+              SubLevel1.class,
+              SubLevel2.class,
+              SubClassOfInvokerClass.class)
+          .addProgramClassFileData(InvokerClassDump.dumpNonVerifying())
+          .setMinApi(parameters.getApiLevel())
+          .addKeepMainRule(MainClassFailing.class)
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                diagnostics.assertErrorMessageThatMatches(containsString("Illegal invoke-super"));
+              });
+      fail("Expected compilation to fail");
+    } catch (CompilationFailedException e) {
+      // Expected compilation failure.
+    }
+  }
+
+  /** Copy of {@ref java.util.function.Consumer} to allow tests to run on early versions of art. */
+  interface Consumer<T> {
+
+    void accept(T item);
+  }
+
+  static class Super {
+
+    public void superMethod() {
+      System.out.println("superMethod in Super");
+    }
+
+    public void otherSuperMethod() {
+      System.out.println("otherSuperMethod in Super");
+    }
+  }
+
+  static class SubLevel1 extends Super {
+
+    @Override
+    public void superMethod() {
+      System.out.println("superMethod in SubLevel1");
+    }
+
+    public void subLevel1Method() {
+      System.out.println("subLevel1Method in SubLevel1");
+    }
+
+    public void otherSuperMethod() {
+      System.out.println("otherSuperMethod in SubLevel1");
+    }
+
+    public void callOtherSuperMethod() {
+      System.out.print("From SubLevel1: ");
+      super.otherSuperMethod();
+    }
+  }
+
+  static class SubClassOfInvokerClass extends InvokerClass {
+
+    public void subLevel2Method() {
+      System.out.println("subLevel2Method in SubClassOfInvokerClass");
+    }
+  }
+
+  static class SubLevel2 extends SubLevel1 {
+
+    @Override
+    public void superMethod() {
+      System.out.println("superMethod in SubLevel2");
+    }
+
+    @Override
+    public void subLevel1Method() {
+      System.out.println("subLevel1Method in SubLevel2");
+    }
+
+    public void subLevel2Method() {
+      System.out.println("subLevel2Method in SubLevel2");
+    }
+
+    public void otherSuperMethod() {
+      System.out.println("otherSuperMethod in SubLevel2");
+    }
+
+    public void callOtherSuperMethodIndirect() {
+      callOtherSuperMethod();
+    }
+  }
+
+  static class MainClass {
+
+    private static void tryInvoke(Consumer<InvokerClass> function) {
+      InvokerClass invoker = new InvokerClass();
+      try {
+        function.accept(invoker);
+      } catch (Throwable e) {
+        System.out.println(e.getClass().getCanonicalName());
+      }
+    }
+
+    public static void main(String... args) {
+      tryInvoke(InvokerClass::invokeSuperMethodOnSuper);
+      tryInvoke(InvokerClass::invokeSuperMethodOnSubLevel1);
+      tryInvoke(InvokerClass::invokeSuperMethodOnSubLevel2);
+      tryInvoke(InvokerClass::invokeSubLevel1MethodOnSuper);
+      tryInvoke(InvokerClass::invokeSubLevel1MethodOnSubLevel1);
+      tryInvoke(InvokerClass::invokeSubLevel1MethodOnSubLevel2);
+      tryInvoke(InvokerClass::invokeSubLevel2MethodOnSubLevel2);
+      tryInvoke(InvokerClass::callOtherSuperMethodIndirect);
+    }
+  }
+
+  static class MainClassFailing {
+
+    private static void tryInvoke(java.util.function.Consumer<InvokerClass> function) {
+      InvokerClass invoker = new InvokerClass();
+      try {
+        function.accept(invoker);
+      } catch (Throwable e) {
+        System.out.println(e.getClass().getCanonicalName());
+      }
+    }
+
+    public static void main(String... args) {
+      tryInvoke(InvokerClass::invokeSubLevel2MethodOnSubClassOfInvokerClass);
+    }
+  }
+
+  /**
+   * This class is a stub class needed to compile the dependent Java classes. The actual
+   * implementation that will be used at runtime is generated by {@link InvokerClassDump}.
+   */
+  static class InvokerClass extends SubLevel2 {
+
+    public void invokeSuperMethodOnSubLevel2() {
+      stubIsUnreachable();
+    }
+
+    public void invokeSuperMethodOnSubLevel1() {
+      stubIsUnreachable();
+    }
+
+    public void invokeSuperMethodOnSuper() {
+      stubIsUnreachable();
+    }
+
+    public void invokeSubLevel1MethodOnSubLevel2() {
+      stubIsUnreachable();
+    }
+
+    public void invokeSubLevel1MethodOnSubLevel1() {
+      stubIsUnreachable();
+    }
+
+    public void invokeSubLevel1MethodOnSuper() {
+      stubIsUnreachable();
+    }
+
+    public void invokeSubLevel2MethodOnSubLevel2() {
+      stubIsUnreachable();
+    }
+
+    public void invokeSubLevel2MethodOnSubClassOfInvokerClass() {
+      stubIsUnreachable();
+    }
+
+    private static void stubIsUnreachable() {
+      throw new RuntimeException("Stub should never be called.");
+    }
+  }
+
+  // This modifies the above {@link InvokerClass} with invoke-special for the corresponding methods.
+  static class InvokerClassDump implements Opcodes {
+
+    public static byte[] dumpVerifying() throws Exception {
+      return dump(true);
+    }
+
+    public static byte[] dumpNonVerifying() throws Exception {
+      return dump(false);
+    }
+
+    static byte[] dump(boolean verifying) throws Exception {
+      return transformer(InvokerClass.class)
+          .addClassTransformer(
+              new ClassTransformer() {
+                @Override
+                public MethodVisitor visitMethod(
+                    int access,
+                    String name,
+                    String descriptor,
+                    String signature,
+                    String[] exceptions) {
+                  // Keep the constructor as is.
+                  if (name.equals("<init>")) {
+                    return super.visitMethod(access, name, descriptor, signature, exceptions);
+                  }
+                  // Remove all methods not in the form of invokeXonY.
+                  if (!name.startsWith("invoke")) {
+                    return null;
+                  }
+                  // If dumping valid methods drop the invoke on a subclass, otherwise drop all
+                  // others.
+                  if (verifying == name.equals("invokeSubLevel2MethodOnSubClassOfInvokerClass")) {
+                    return null;
+                  }
+                  // Replace the body of invokeXonY methods by invoke of X on class Y.
+                  MethodVisitor mv =
+                      super.visitMethod(access, name, descriptor, signature, exceptions);
+                  int split = name.indexOf("On");
+                  assertTrue(split > 0);
+                  String targetMethodRaw = name.substring("invoke".length(), split);
+                  String targetMethod =
+                      targetMethodRaw.substring(0, 1).toLowerCase() + targetMethodRaw.substring(1);
+                  String targetHolderRaw = name.substring(split + 2);
+                  String targetHolderType =
+                      InvokeSuperTest.class.getTypeName() + "$" + targetHolderRaw;
+                  String targetHolderName =
+                      DescriptorUtils.getBinaryNameFromJavaType(targetHolderType);
+                  mv.visitCode();
+                  mv.visitVarInsn(ALOAD, 0);
+                  mv.visitMethodInsn(INVOKESPECIAL, targetHolderName, targetMethod, "()V", false);
+                  mv.visitInsn(RETURN);
+                  mv.visitMaxs(1, 1);
+                  mv.visitEnd();
+                  return null;
+                }
+              })
+          .transform();
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
index 67191c8..ecdaa6f 100644
--- a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
@@ -58,7 +58,7 @@
   }
 
   @Test(expected = CompilationFailedException.class)
-  public void testCfInvokeOnStaticInterfaceMethod_failed()
+  public void testCfInvokeOnStaticInterfaceMethod_errorNotAllowed()
       throws ExecutionException, CompilationFailedException, IOException {
     testForR8(parameters.getBackend())
         .addProgramClasses(I.class)
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/Consumer.java b/src/test/java/com/android/tools/r8/graph/invokesuper/Consumer.java
deleted file mode 100644
index b323136..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/Consumer.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-/**
- * Copy of {@ref java.util.function.Consumer} to allow tests to run on early versions of art.
- */
-public interface Consumer<T> {
-
-  void accept(T item);
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClass.java b/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClass.java
deleted file mode 100644
index e0c9f93..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClass.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-import com.android.tools.r8.errors.Unreachable;
-
-/**
- * This class is a stub class needed to compile the dependent Java classes. The actual
- * implementation that will be used at runtime is generated by {@link InvokerClassDump} and {@link
- * InvokerClassFailingDump}.
- */
-public class InvokerClass extends SubLevel2 {
-
-  public void invokeSuperMethodOnSubLevel2() {
-    stubIsUnreachable();
-  }
-
-  public void invokeSuperMethodOnSubLevel1() {
-    stubIsUnreachable();
-  }
-
-  public void invokeSuperMethodOnSuper() {
-    stubIsUnreachable();
-  }
-
-  public void invokeSubLevel1MethodOnSubLevel2() {
-    stubIsUnreachable();
-  }
-
-  public void invokeSubLevel1MethodOnSubLevel1() {
-    stubIsUnreachable();
-  }
-
-  public void invokeSubLevel1MethodOnSuper() {
-    stubIsUnreachable();
-  }
-
-  public void invokeSubLevel2MethodOnSubLevel2() {
-    stubIsUnreachable();
-  }
-
-  public void invokeSubLevel2MethodOnSubClassOfInvokerClass() {
-    stubIsUnreachable();
-  }
-
-  private static void stubIsUnreachable() {
-    throw new Unreachable("Stub should never be called.");
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassDump.java b/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassDump.java
deleted file mode 100644
index c01700d..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassDump.java
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-/**
- * This is a modified version of {@link InvokerClass} with invoke special instructions corresponding
- * to the methods' names.
- */
-public class InvokerClassDump implements Opcodes {
-
-  public static byte[] dump() throws Exception {
-
-    ClassWriter cw = new ClassWriter(0);
-    FieldVisitor fv;
-    MethodVisitor mv;
-    AnnotationVisitor av0;
-
-    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "com/android/tools/r8/graph/invokesuper/InvokerClass",
-        null, "com/android/tools/r8/graph/invokesuper/SubLevel2", null);
-
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
-          "<init>", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "invokeSuperMethodOnSubLevel2", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
-          "superMethod", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "invokeSuperMethodOnSubLevel1", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel1",
-          "superMethod", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "invokeSuperMethodOnSuper", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/Super",
-          "superMethod", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel1MethodOnSubLevel2", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
-          "subLevel1Method", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel1MethodOnSubLevel1", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel1",
-          "subLevel1Method", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel1MethodOnSuper", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/Super",
-          "subLevel1Method", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel2MethodOnSubLevel2", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
-          "subLevel2Method", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    // The below fails to verify on the JavaVM, so we cannot test it there.
-    // {
-    //   mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel2MethodOnSubClassOfInvokerClass", "()V",
-    //       null,
-    //       null);
-    //   mv.visitCode();
-    //   mv.visitVarInsn(ALOAD, 0);
-    //   mv.visitMethodInsn(INVOKESPECIAL,
-    //       "com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass",
-    //       "subLevel2Method", "()V", false);
-    //   mv.visitInsn(RETURN);
-    //   mv.visitMaxs(1, 1);
-    //   mv.visitEnd();
-    // }
-    cw.visitEnd();
-
-    return cw.toByteArray();
-  }
-}
-
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassFailingDump.java b/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassFailingDump.java
deleted file mode 100644
index 18eebe4..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/InvokerClassFailingDump.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-/**
- /**
- * This is a modified version of {@link InvokerClass} with invoke special instructions corresponding
- * to the methods' names.
- * <p>
- * This class contains methods that do not verify on Java VMs.
- */
-public class InvokerClassFailingDump implements Opcodes {
-
-  public static byte[] dump() throws Exception {
-
-    ClassWriter cw = new ClassWriter(0);
-    FieldVisitor fv;
-    MethodVisitor mv;
-    AnnotationVisitor av0;
-
-    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "com/android/tools/r8/graph/invokesuper/InvokerClass",
-        null, "com/android/tools/r8/graph/invokesuper/SubLevel2", null);
-
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "com/android/tools/r8/graph/invokesuper/SubLevel2",
-          "<init>", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "invokeSubLevel2MethodOnSubClassOfInvokerClass", "()V", null,
-          null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL,
-          "com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass",
-          "subLevel2Method", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    cw.visitEnd();
-
-    return cw.toByteArray();
-  }
-}
-
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/MainClass.java b/src/test/java/com/android/tools/r8/graph/invokesuper/MainClass.java
deleted file mode 100644
index 80e4d09..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/MainClass.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-public class MainClass {
-
-  private static void tryInvoke(Consumer<InvokerClass> function) {
-    InvokerClass invoker = new InvokerClass();
-    try {
-      function.accept(invoker);
-    } catch (Throwable e) {
-      System.out.println(e.getClass().getCanonicalName());
-    }
-  }
-
-  public static void main(String... args) {
-    tryInvoke(InvokerClass::invokeSuperMethodOnSuper);
-    tryInvoke(InvokerClass::invokeSuperMethodOnSubLevel1);
-    tryInvoke(InvokerClass::invokeSuperMethodOnSubLevel2);
-    tryInvoke(InvokerClass::invokeSubLevel1MethodOnSuper);
-    tryInvoke(InvokerClass::invokeSubLevel1MethodOnSubLevel1);
-    tryInvoke(InvokerClass::invokeSubLevel1MethodOnSubLevel2);
-    tryInvoke(InvokerClass::invokeSubLevel2MethodOnSubLevel2);
-    tryInvoke(InvokerClass::callOtherSuperMethodIndirect);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/MainClassFailing.java b/src/test/java/com/android/tools/r8/graph/invokesuper/MainClassFailing.java
deleted file mode 100644
index c662ff3..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/MainClassFailing.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-import java.util.function.Consumer;
-
-public class MainClassFailing {
-
-  private static void tryInvoke(Consumer<InvokerClass> function) {
-    InvokerClass invoker = new InvokerClass();
-    try {
-      function.accept(invoker);
-    } catch (Throwable e) {
-      System.out.println(e.getClass().getCanonicalName());
-    }
-  }
-
-  public static void main(String... args) {
-    tryInvoke(InvokerClass::invokeSubLevel2MethodOnSubClassOfInvokerClass);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel1.java b/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel1.java
deleted file mode 100644
index 50fa633..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel1.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-public class SubLevel1 extends Super {
-
-  @Override
-  public void superMethod() {
-    System.out.println("superMethod in SubLevel1");
-  }
-
-  public void subLevel1Method() {
-    System.out.println("subLevel1Method in SubLevel1");
-  }
-
-  public void otherSuperMethod() {
-    System.out.println("otherSuperMethod in SubLevel1");
-  }
-
-  public void callOtherSuperMethod() {
-    System.out.print("From SubLevel1: ");
-    super.otherSuperMethod();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel2.java b/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel2.java
deleted file mode 100644
index 9cb04ec..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/SubLevel2.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-public class SubLevel2 extends SubLevel1 {
-
-  @Override
-  public void superMethod() {
-    System.out.println("superMethod in SubLevel2");
-  }
-
-  @Override
-  public void subLevel1Method() {
-    System.out.println("subLevel1Method in SubLevel2");
-  }
-
-  public void subLevel2Method() {
-    System.out.println("subLevel2Method in SubLevel2");
-  }
-
-  public void otherSuperMethod() {
-    System.out.println("otherSuperMethod in SubLevel2");
-  }
-
-  public void callOtherSuperMethodIndirect() {
-    callOtherSuperMethod();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass.java b/src/test/java/com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass.java
deleted file mode 100644
index 77d6aab..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/SubclassOfInvokerClass.java
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-public class SubclassOfInvokerClass extends InvokerClass {
-
-  public void subLevel2Method() {
-    System.out.println("subLevel2Method in SubclassOfInvokerClass");
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper/Super.java b/src/test/java/com/android/tools/r8/graph/invokesuper/Super.java
deleted file mode 100644
index 51596fe..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokesuper/Super.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.invokesuper;
-
-public class Super {
-
-  public void superMethod() {
-    System.out.println("superMethod in Super");
-  }
-
-  public void otherSuperMethod() {
-    System.out.println("otherSuperMethod in Super");
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 3127150..82362aa 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
@@ -80,10 +79,6 @@
             .compile()
             .inspect(this::inspect);
 
-    // TODO(b/112437944): Should never allow dynamicMethod() to be inlined unless MethodToInvoke is
-    //  guaranteed to be different from MethodToInvoke.BUILD_MESSAGE_INFO.
-    assumeTrue(mains.size() > 1);
-
     for (String main : mains) {
       result.run(parameters.getRuntime(), main).assertSuccessWithOutput(getExpectedOutput(main));
     }
@@ -160,39 +155,19 @@
   }
 
   private void inspect(CodeInspector outputInspector) {
-    // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
-    //  is kept.
-    assertThat(outputInspector.clazz(LITE_BUILDER), isPresent());
-
-    // TODO(b/112437944): Should be absent.
+    boolean primitivesBuilderShouldBeLive =
+        mains.contains("proto2.BuilderWithReusedSettersTestClass");
     assertThat(
-        outputInspector.clazz("com.android.tools.r8.proto2.TestProto$NestedMessage$Builder"),
-        isNestedMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
-
-    // TODO(b/112437944): Should be absent.
-    assertThat(
-        outputInspector.clazz("com.android.tools.r8.proto2.TestProto$OuterMessage$Builder"),
-        isOuterMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
-
-    // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
-    //  is kept.
+        outputInspector.clazz(LITE_BUILDER),
+        primitivesBuilderShouldBeLive ? isPresent() : not(isPresent()));
     assertThat(
         outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
-        isPrimitivesBuilderUsed(mains) ? isPresent() : not(isPresent()));
-  }
-
-  private static boolean isNestedMessageBuilderUsed(List<String> mains) {
-    return mains.contains("proto2.BuilderWithProtoBuilderSetterTestClass")
-        || mains.contains("proto2.BuilderWithProtoSetterTestClass");
-  }
-
-  private static boolean isOuterMessageBuilderUsed(List<String> mains) {
-    return isNestedMessageBuilderUsed(mains);
-  }
-
-  private static boolean isPrimitivesBuilderUsed(List<String> mains) {
-    return mains.contains("proto2.BuilderWithOneofSetterTestClass")
-        || mains.contains("proto2.BuilderWithPrimitiveSettersTestClass")
-        || mains.contains("proto2.BuilderWithReusedSettersTestClass");
+        primitivesBuilderShouldBeLive ? isPresent() : not(isPresent()));
+    assertThat(
+        outputInspector.clazz("com.android.tools.r8.proto2.TestProto$OuterMessage$Builder"),
+        not(isPresent()));
+    assertThat(
+        outputInspector.clazz("com.android.tools.r8.proto2.TestProto$NestedMessage$Builder"),
+        not(isPresent()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index aed2c26..eb62734 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -79,7 +79,6 @@
             .addKeepMainRule("proto2.TestClass")
             .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
             .addKeepRules(allGeneratedMessageLiteSubtypesAreInstantiatedRule())
-            .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
             .addOptionsModification(
                 options -> {
                   options.enableFieldBitAccessAnalysis = true;
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index 19b3857..a1ecab4 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -77,14 +77,6 @@
         "}");
   }
 
-  static String alwaysInlineNewSingularGeneratedExtensionRule() {
-    return StringUtils.lines(
-        "-alwaysinline class com.google.protobuf.GeneratedMessageLite {",
-        "  com.google.protobuf.GeneratedMessageLite$GeneratedExtension"
-            + " newSingularGeneratedExtension(...);",
-        "}");
-  }
-
   static String keepAllProtosRule() {
     return StringUtils.lines(
         "-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }");
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
index b35254e..2d1f0e5 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
 import com.android.tools.r8.utils.InternalOptions;
@@ -148,24 +149,25 @@
     nodes.add(n5);
     nodes.add(n6);
 
-    Set<Node> wave = Sets.newIdentityHashSet();
+    CallGraph callGraph = new CallGraph(nodes, null);
+    Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n1));
-    assertThat(wave, hasItem(n5));
+    assertThat(wave, hasItem(n1.method));
+    assertThat(wave, hasItem(n5.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2));
-    assertThat(wave, hasItem(n6));
+    assertThat(wave, hasItem(n2.method));
+    assertThat(wave, hasItem(n6.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n3));
-    assertThat(wave, hasItem(n4));
+    assertThat(wave, hasItem(n3.method));
+    assertThat(wave, hasItem(n4.method));
     assertTrue(nodes.isEmpty());
   }
 
@@ -200,24 +202,25 @@
     CycleEliminator cycleEliminator = new CycleEliminator(nodes, options);
     assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
 
-    Set<Node> wave = Sets.newIdentityHashSet();
+    CallGraph callGraph = new CallGraph(nodes, null);
+    Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n1));
-    assertThat(wave, hasItem(n5));
+    assertThat(wave, hasItem(n1.method));
+    assertThat(wave, hasItem(n5.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2));
-    assertThat(wave, hasItem(n6));
+    assertThat(wave, hasItem(n2.method));
+    assertThat(wave, hasItem(n6.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n3));
-    assertThat(wave, hasItem(n4));
+    assertThat(wave, hasItem(n3.method));
+    assertThat(wave, hasItem(n4.method));
     assertTrue(nodes.isEmpty());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
index 880139f..8b027e4 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -126,22 +126,22 @@
     assertNotNull(m4);
     assertNotNull(m5);
 
-    Set<Node> wave = Sets.newIdentityHashSet();
+    Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
 
-    PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+    wave.addAll(pg.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(m1));
-    assertThat(wave, hasItem(m5));
+    assertThat(wave, hasItem(m1.method));
+    assertThat(wave, hasItem(m5.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+    wave.addAll(pg.extractRoots());
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(m2));
+    assertThat(wave, hasItem(m2.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+    wave.addAll(pg.extractRoots());
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(m4));
+    assertThat(wave, hasItem(m4.method));
     assertTrue(pg.nodes.isEmpty());
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 25c4a53..e5c7948 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -83,6 +83,7 @@
           ObjectsMethods.class,
           OptionalMethods.class,
           ShortMethods.class,
+          StreamMethods.class,
           StringMethods.class);
 
   final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/StreamMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/StreamMethods.java
new file mode 100644
index 0000000..65e6780
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/StreamMethods.java
@@ -0,0 +1,13 @@
+// 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.desugar.backports;
+
+import java.util.stream.Stream;
+
+public class StreamMethods {
+  public static <T> Stream<T> ofNullable(T t) {
+    return t == null ? Stream.empty() : Stream.of(t);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
index 4fe7fde..9b424d3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -68,6 +68,7 @@
   static class Host {
     private static final Companion companion = new Companion();
 
+    @NeverClassInline
     static class Companion {
       @NeverInline
       public void foo(Object arg) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java
new file mode 100644
index 0000000..596c9e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java
@@ -0,0 +1,90 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithAccessibleStaticGetTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlineInstanceInitializerWithAccessibleStaticGetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlineInstanceInitializerWithAccessibleStaticGetTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+    assertThat(inspector.clazz(CandidateBase.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Environment.VALUE = true;
+      System.out.print(new Candidate().get());
+      Environment.VALUE = false;
+      System.out.println(new Candidate().get());
+    }
+  }
+
+  @NeverMerge
+  static class CandidateBase {
+
+    final String f;
+
+    CandidateBase() {
+      if (Environment.VALUE) {
+        f = "Hello";
+      } else {
+        f = " world!";
+      }
+    }
+  }
+
+  static class Candidate extends CandidateBase {
+
+    @NeverInline
+    String get() {
+      return f;
+    }
+  }
+
+  static class Environment {
+
+    static boolean VALUE;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java
new file mode 100644
index 0000000..c0f45d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java
@@ -0,0 +1,83 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithCheckCastTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlineInstanceInitializerWithCheckCastTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlineInstanceInitializerWithCheckCastTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+    assertThat(inspector.clazz(CandidateBase.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.print(new Candidate("Hello").get());
+      System.out.println(new Candidate(" world!").get());
+    }
+  }
+
+  @NeverMerge
+  static class CandidateBase {
+
+    final String f;
+
+    CandidateBase(Object o) {
+      f = (String) o;
+    }
+  }
+
+  static class Candidate extends CandidateBase {
+
+    Candidate(Object o) {
+      super(o);
+    }
+
+    @NeverInline
+    String get() {
+      return f;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.java
new file mode 100644
index 0000000..912374d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithIfTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlineInstanceInitializerWithIfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlineInstanceInitializerWithIfTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+    assertThat(inspector.clazz(CandidateBase.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.print(new Candidate(true).get());
+      System.out.println(new Candidate(false).get());
+    }
+  }
+
+  @NeverMerge
+  static class CandidateBase {
+
+    final String f;
+
+    CandidateBase(boolean b) {
+      if (b) {
+        f = "Hello";
+      } else {
+        f = " world!";
+      }
+    }
+  }
+
+  static class Candidate extends CandidateBase {
+
+    Candidate(boolean b) {
+      super(b);
+    }
+
+    @NeverInline
+    String get() {
+      return f;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
new file mode 100644
index 0000000..6d70ba5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
@@ -0,0 +1,75 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.classinliner.testclasses.ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses;
+import com.android.tools.r8.ir.optimize.classinliner.testclasses.ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.CandidateBase;
+import com.android.tools.r8.ir.optimize.classinliner.testclasses.ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.Environment;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithInaccessibleStaticGetTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlineInstanceInitializerWithInaccessibleStaticGetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(
+            ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.class,
+            ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.class)
+        .addKeepMainRule(TestClass.class)
+        .allowClassInlinerGracefulExit()
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Candidate.class), isPresent());
+    assertThat(inspector.clazz(CandidateBase.class), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Environment.setValue(true);
+      System.out.print(new Candidate().get());
+      Environment.setValue(false);
+      System.out.println(new Candidate().get());
+    }
+  }
+
+  static class Candidate extends CandidateBase {
+
+    @NeverInline
+    String get() {
+      return f;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java
new file mode 100644
index 0000000..c86e0fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java
@@ -0,0 +1,93 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Candidate.class), isPresent());
+    assertThat(inspector.clazz(CandidateBase.class), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new Candidate().get());
+    }
+  }
+
+  @NeverMerge
+  static class CandidateBase {
+
+    CandidateBase() {
+      Escape.escape(getReceiver());
+    }
+
+    @NeverInline
+    Object getReceiver() {
+      return System.currentTimeMillis() >= 0 ? this : null;
+    }
+  }
+
+  static class Candidate extends CandidateBase {
+
+    @NeverInline
+    @NeverPropagateValue
+    String get() {
+      return "Hello world!";
+    }
+  }
+
+  static class Escape {
+
+    @NeverInline
+    static void escape(Object o) {
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(o);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java
new file mode 100644
index 0000000..43e2998
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java
@@ -0,0 +1,83 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithInstanceOfTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlineInstanceInitializerWithInstanceOfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlineInstanceInitializerWithInstanceOfTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+    assertThat(inspector.clazz(CandidateBase.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.print(new Candidate("42").get() ? "Hello" : " world!");
+      System.out.println(new Candidate(42).get() ? "Hello" : " world!");
+    }
+  }
+
+  @NeverMerge
+  static class CandidateBase {
+
+    final boolean f;
+
+    CandidateBase(Object o) {
+      f = o instanceof String;
+    }
+  }
+
+  static class Candidate extends CandidateBase {
+
+    Candidate(Object o) {
+      super(o);
+    }
+
+    @NeverInline
+    boolean get() {
+      return f;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineSingletonFieldOfOtherTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineSingletonFieldOfOtherTypeTest.java
new file mode 100644
index 0000000..18739a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineSingletonFieldOfOtherTypeTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 ClassInlineSingletonFieldOfOtherTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlineSingletonFieldOfOtherTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlineSingletonFieldOfOtherTypeTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+    assertThat(inspector.clazz(Container.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Container.getInstance().greet();
+    }
+  }
+
+  static class Candidate {
+
+    @NeverInline
+    void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+
+  static class Container {
+
+    static Candidate INSTANCE = new Candidate();
+
+    static Candidate getInstance() {
+      return INSTANCE;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 325bdd5..1058615 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -11,6 +11,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -34,7 +35,6 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -84,9 +84,9 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableInliningAnnotations()
+            .enableSideEffectAnnotations()
             .addKeepMainRule(main)
             .addKeepAttributes("LineNumberTable")
-            .addOptionsModification(this::configure)
             .allowAccessModification()
             .noMinification()
             .run(main)
@@ -168,10 +168,7 @@
 
     AndroidApp compiled =
         compileWithR8(
-            builder.build(),
-            getProguardConfig(mainClass.name),
-            this::configure,
-            parameters.getBackend());
+            builder.build(), getProguardConfig(mainClass.name), null, parameters.getBackend());
 
     // Check that the code fails with an IncompatibleClassChangeError with Java.
     ProcessResult javaResult =
@@ -200,9 +197,9 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableInliningAnnotations()
+            .enableSideEffectAnnotations()
             .addKeepMainRule(main)
             .addKeepAttributes("LineNumberTable")
-            .addOptionsModification(this::configure)
             .allowAccessModification()
             .noMinification()
             .run(main)
@@ -244,7 +241,6 @@
                   // TODO(b/143129517, 141719453): The limit seems to only be needed for DEX...
                   o.classInliningInstructionLimit = 100;
                   o.classInliningInstructionAllowance = 1000;
-                  configure(o);
                 })
             .allowAccessModification()
             .noMinification()
@@ -299,7 +295,6 @@
                 o -> {
                   // TODO(b/141719453): Identify single instances instead of increasing the limit.
                   o.classInliningInstructionLimit = 20;
-                  configure(o);
                 })
             .allowAccessModification()
             .enableInliningAnnotations()
@@ -323,12 +318,8 @@
             .filter(name -> name.contains(LAMBDA_CLASS_NAME_PREFIX))
             .collect(Collectors.toList()));
     assertEquals(expectedTypes, collectTypes(clazz.uniqueMethodWithName("testStatefulLambda")));
-
-    // TODO(b/120814598): Should be 0. Lambdas are not class inlined because parameter usage is not
-    // available for each lambda constructor.
-    assertEquals(
-        3,
-        inspector.allClasses().stream().filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
+    assertTrue(
+        inspector.allClasses().stream().noneMatch(ClassSubject::isSynthesizedJavaLambdaClass));
   }
 
   private String getProguardConfig(String main) {
@@ -338,8 +329,4 @@
         "-allowaccessmodification",
         "-keepattributes LineNumberTable");
   }
-
-  private void configure(InternalOptions options) {
-    options.enableSideEffectAnalysis = false;
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java
index 4cdbc84..b8b159c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java
@@ -31,7 +31,8 @@
 
   @Parameters(name = "{1}, enable class inlining: {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public ClassInlinerWithSimpleSuperTypeTest(
@@ -48,7 +49,7 @@
         .addOptionsModification(options -> options.enableClassInlining = enableClassInlining)
         .enableInliningAnnotations()
         .enableMergeAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyCandidateIsClassInlined)
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
index dcfb5eb..ae5ef86 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.code;
 
+import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverInline;
 
 public class C {
@@ -27,16 +28,19 @@
     }
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   public static int method1() {
     return new L(1).x;
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   public static int method2() {
     return new L(1).getX();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   public static int method3() {
     return F.I.getX();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
new file mode 100644
index 0000000..50eeac4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
@@ -0,0 +1,33 @@
+// 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.classinliner.testclasses;
+
+import com.android.tools.r8.NeverMerge;
+
+public class ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses {
+
+  @NeverMerge
+  public static class CandidateBase {
+
+    public final String f;
+
+    public CandidateBase() {
+      if (Environment.VALUE) {
+        f = "Hello";
+      } else {
+        f = " world!";
+      }
+    }
+  }
+
+  public static class Environment {
+
+    /*package-private*/ static boolean VALUE;
+
+    public static void setValue(boolean value) {
+      VALUE = value;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
index 9b7835d..86cf25e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.trivial;
 
+import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverInline;
 
 public class TrivialTestClass {
@@ -51,6 +52,7 @@
     System.out.println(o.getA() + o.getB() + o.getConcat());
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   private void testEmptyClass() {
     new EmptyClass();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
index 654e0f2..be2ffe2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import java.io.IOException;
 import java.util.Arrays;
@@ -27,9 +26,6 @@
 @RunWith(Parameterized.class)
 public class InlinerShouldNotInlineDefinitelyNullTest extends TestBase {
 
-  public static String EXPECTED_STACK_TRACE = StringUtils.joinLines(
-      "java.lang.NullPointerException", "  at " + Main.class.getTypeName() + ".main(");
-
   public static class A {
 
     void foo() {
@@ -78,13 +74,13 @@
                           .anyMatch(InstructionSubject::isThrow));
                 })
             .run(parameters.getRuntime(), Main.class)
-            .assertFailure();
+            .assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"))
+            .inspectStackTrace(
+                stackTrace -> {
+                  assertThat(
+                      stackTrace.toString(), containsString(Main.class.getTypeName() + ".main("));
+                });
     String[] split = result.proguardMap().split("\n");
     assertTrue(Arrays.stream(split).noneMatch(l -> l.contains(A.class.getTypeName() + ".foo")));
-    assertThat(
-        StringUtils.joinLines(result.retrace())
-            .replace("\tat", "  at")
-            .replace(": throw with null exception", ""),
-        containsString(EXPECTED_STACK_TRACE));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
index 58ad103..b0bf180 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -70,12 +70,10 @@
         testDefinitelyNotNullMethodSubject
             .streamInstructions()
             .noneMatch(InstructionSubject::isInstanceGet));
-    // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
-    //  ends up being unused.
     assertTrue(
         testDefinitelyNotNullMethodSubject
             .streamInstructions()
-            .anyMatch(InstructionSubject::isNewInstance));
+            .noneMatch(InstructionSubject::isNewInstance));
 
     // Verify that all instance-get instructions in testMaybeNull() has been removed by member value
     // propagation.
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index a3ec1a2..177a8a5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -37,15 +36,11 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 import org.junit.Assume;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
-@RunWith(Parameterized.class)
 public abstract class AbstractR8KotlinTestBase extends KotlinTestBase {
 
   // This is the name of the Jasmin-generated class which contains the "main" method which will
@@ -57,9 +52,9 @@
   private final List<Path> classpath = new ArrayList<>();
   private final List<Path> extraClasspath = new ArrayList<>();
 
-  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
-  public static Collection<Object[]> data() {
-    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  // Some tests defined in subclasses, e.g., Metadata tests, don't care about access relaxation.
+  protected AbstractR8KotlinTestBase(KotlinTargetVersion kotlinTargetVersion) {
+    this(kotlinTargetVersion, false);
   }
 
   protected AbstractR8KotlinTestBase(
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 38ff405..5d55ba2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -27,15 +28,24 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase {
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   public KotlinClassInlinerTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index b32bc04..6087795 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -11,15 +11,25 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.google.common.base.Predicates;
+import java.util.Collection;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class KotlinClassStaticizerTest extends AbstractR8KotlinTestBase {
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   public KotlinClassStaticizerTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
index 8eb6a2e..35e3109 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
@@ -17,8 +17,10 @@
 import java.util.Collection;
 import java.util.function.Consumer;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class KotlinDuplicateAnnotationTest extends AbstractR8KotlinTestBase {
   private static final String FOLDER = "duplicate_annotation";
   private static final String MAIN = FOLDER + ".MainKt";
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
index 717462c..dc917ab 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -16,8 +16,10 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.Collection;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class KotlinIntrinsicsInlineTest extends AbstractR8KotlinTestBase {
   private static final String FOLDER = "intrinsics";
   private static final String MAIN = FOLDER + ".InlineKt";
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
index b85f583..6ad7046 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Source;
 import com.android.tools.r8.utils.StringUtils;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -38,6 +39,7 @@
   }
 
   @Test
+  @Ignore("b/145985445")
   public void testParsingNoInlineSources() {
     String annotationData =
         StringUtils.join(
@@ -66,6 +68,7 @@
   }
 
   @Test
+  @Ignore("b/145985445")
   public void testParsingSimpleStrata() {
     // Taken from src/test/examplesKotlin/retrace/mainKt
     String annotationData =
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
index 95ec356..77de408 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
@@ -9,14 +9,25 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.util.Collection;
 import java.util.function.Consumer;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class KotlinUnusedArgumentsInLambdasTest extends AbstractR8KotlinTestBase {
+
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   private Consumer<InternalOptions> optionsModifier =
     o -> {
       o.enableInlining = true;
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
index e58c00c..c82a4a4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -17,10 +18,19 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.util.Collection;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class KotlinUnusedSingletonTest extends AbstractR8KotlinTestBase {
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   private static final String printlnSignature =
       "void java.io.PrintStream.println(java.lang.Object)";
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index cfeb611..e5ce02c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -17,17 +17,22 @@
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import java.nio.file.Path;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.function.Consumer;
 import org.junit.Assert;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class R8KotlinAccessorTest extends AbstractR8KotlinTestBase {
 
   private static final String JAVA_LANG_STRING = "java.lang.String";
@@ -65,6 +70,11 @@
   private Consumer<InternalOptions> disableClassStaticizer =
       opts -> opts.enableClassStaticizer = false;
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   public R8KotlinAccessorTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 7e16b2f..59c92ff 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -8,14 +8,19 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.function.Consumer;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class R8KotlinDataClassTest extends AbstractR8KotlinTestBase {
 
   private static final TestKotlinDataClass TEST_DATA_CLASS =
@@ -38,6 +43,11 @@
 
   private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   public R8KotlinDataClassTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index c74b790..7a9635e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -6,10 +6,12 @@
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+import java.util.Collection;
 import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -21,6 +23,11 @@
   private static final TestKotlinDataClass KOTLIN_INTRINSICS_CLASS =
       new TestKotlinDataClass("kotlin.jvm.internal.Intrinsics");
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   public R8KotlinIntrinsicsTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index fcb2872..f572789 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -10,13 +10,18 @@
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.util.Collection;
 import java.util.function.Consumer;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class R8KotlinPropertiesTest extends AbstractR8KotlinTestBase {
 
   private static final String PACKAGE_NAME = "properties";
@@ -89,6 +94,11 @@
         o.enableClassStaticizer = false;
       };
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   public R8KotlinPropertiesTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index b9ee37a..aebb0cf 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -7,16 +7,26 @@
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.util.Collection;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class SimplifyIfNotNullKotlinTest extends AbstractR8KotlinTestBase {
   private static final String FOLDER = "non_null";
   private static final String STRING = "java.lang.String";
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   public SimplifyIfNotNullKotlinTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
new file mode 100644
index 0000000..1c46c19
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
@@ -0,0 +1,91 @@
+// 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.kotlin.lambda;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KotlinLambdaMergerValidationTest extends AbstractR8KotlinTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
+  }
+
+  public KotlinLambdaMergerValidationTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion, false);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8_excludeKotlinStdlib() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+
+    String pkg = getClass().getPackage().getName();
+    String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg);
+    CfRuntime cfRuntime =
+        parameters.isCfRuntime() ? parameters.getRuntime().asCf() : TestRuntime.getCheckedInJdk9();
+    Path ktClasses =
+        kotlinc(cfRuntime, KOTLINC, targetVersion)
+            .addSourceFiles(getKotlinFileInTest(folder, "b143165163"))
+            .compile();
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+        .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(ktClasses)
+        .addKeepMainRule("**.B143165163Kt")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        // TODO(b/143165163): better not output info like this.
+        .assertInfoMessageThatMatches(containsString("Unrecognized Kotlin lambda"))
+        .assertInfoMessageThatMatches(containsString("unexpected static method"))
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar())
+        .run(parameters.getRuntime(), pkg + ".B143165163Kt")
+        .assertSuccessWithOutputLines("outer foo bar", "outer foo default");
+  }
+
+  @Test
+  public void testR8_includeKotlinStdlib() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+
+    String pkg = getClass().getPackage().getName();
+    String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg);
+    CfRuntime cfRuntime =
+        parameters.isCfRuntime() ? parameters.getRuntime().asCf() : TestRuntime.getCheckedInJdk9();
+    Path ktClasses =
+        kotlinc(cfRuntime, KOTLINC, targetVersion)
+            .addSourceFiles(getKotlinFileInTest(folder, "b143165163"))
+            .compile();
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(ktClasses)
+        .addKeepMainRule("**.B143165163Kt")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        // TODO(b/143165163): better not output info like this.
+        .assertInfoMessageThatMatches(containsString("Unrecognized Kotlin lambda"))
+        .assertInfoMessageThatMatches(containsString("does not implement any interfaces"))
+        .run(parameters.getRuntime(), pkg + ".B143165163Kt")
+        .assertSuccessWithOutputLines("outer foo bar", "outer foo default");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
rename to src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
index d8c059d..bab791c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.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.kotlin;
+package com.android.tools.r8.kotlin.lambda;
 
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -26,8 +27,10 @@
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase {
   private static final String KOTLIN_FUNCTION_IFACE = "Lkotlin/jvm/functions/Function";
   private static final String KOTLIN_FUNCTION_IFACE_STR = "kotlin.jvm.functions.Function";
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
similarity index 68%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java
rename to src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
index da30438..5ba846c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
@@ -1,13 +1,19 @@
 // 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.kotlin;
+package com.android.tools.r8.kotlin.lambda;
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collection;
 import java.util.function.Consumer;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class KotlinLambdaMergingWithReprocessingTest extends AbstractR8KotlinTestBase {
   private Consumer<InternalOptions> optionsModifier =
     o -> {
@@ -16,6 +22,11 @@
       o.enableLambdaMerging = true;
     };
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   public KotlinLambdaMergingWithReprocessingTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithSmallInliningBudgetTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
similarity index 69%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithSmallInliningBudgetTest.java
rename to src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
index 24b0524..446d982 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithSmallInliningBudgetTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
@@ -1,13 +1,19 @@
 // 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.
-package com.android.tools.r8.kotlin;
+package com.android.tools.r8.kotlin.lambda;
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collection;
 import java.util.function.Consumer;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class KotlinLambdaMergingWithSmallInliningBudgetTest extends AbstractR8KotlinTestBase {
   private Consumer<InternalOptions> optionsModifier =
       o -> {
@@ -17,6 +23,11 @@
         o.inliningInstructionAllowance = 3;
       };
 
+  @Parameterized.Parameters(name = "target: {0}, allowAccessModification: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
   public KotlinLambdaMergingWithSmallInliningBudgetTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinxMetadataExtensionsServiceTest.java
similarity index 91%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
rename to src/test/java/com/android/tools/r8/kotlin/lambda/KotlinxMetadataExtensionsServiceTest.java
index ed135b0..26cc829 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinxMetadataExtensionsServiceTest.java
@@ -1,10 +1,10 @@
 // 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.
-package com.android.tools.r8.kotlin;
+package com.android.tools.r8.kotlin.lambda;
 
 import static com.android.tools.r8.ToolHelper.EXAMPLES_KOTLIN_RESOURCE_DIR;
-import static com.android.tools.r8.kotlin.KotlinLambdaMergingTest.kstyle;
+import static com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.kstyle;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -15,9 +15,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Group;
-import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Lambda;
-import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Verifier;
+import com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.Group;
+import com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.Lambda;
+import com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.Verifier;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b143165163.kt b/src/test/java/com/android/tools/r8/kotlin/lambda/b143165163.kt
new file mode 100644
index 0000000..d8d7a49
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b143165163.kt
@@ -0,0 +1,13 @@
+// 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.kotlin.lambda
+
+fun main() {
+  fun String.inner(foo: String, bar: String = "default") {
+    println("$this $foo $bar")
+  }
+  "outer".inner("foo", "bar")
+  "outer".inner("foo")
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index cdb5538..d866cca 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.DescriptorUtils;
 
-abstract class KotlinMetadataTestBase extends KotlinTestBase {
+abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase {
 
   KotlinMetadataTestBase(KotlinTargetVersion targetVersion) {
     super(targetVersion);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
new file mode 100644
index 0000000..d5297d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInClasspathTypeTest.java
@@ -0,0 +1,168 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInClasspathTypeTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRenameInClasspathTypeTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Path baseLibJar;
+  private static Path extLibJar;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String baseLibFolder = PKG_PREFIX + "/classpath_lib_base";
+    baseLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(getKotlinFileInTest(baseLibFolder, "itf"))
+            .compile();
+    String extLibFolder = PKG_PREFIX + "/classpath_lib_ext";
+    extLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(baseLibJar)
+            .addSourceFiles(getKotlinFileInTest(extLibFolder, "impl"))
+            .compile();
+  }
+
+  @Test
+  public void testMetadataInClasspathType_merged() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(baseLibJar)
+            .addProgramFiles(extLibJar)
+            // Keep the Extra class and its interface (which has the method).
+            .addKeepRules("-keep class **.Extra")
+            // Keep the ImplKt extension method which requires metadata
+            // to be called with Kotlin syntax from other kotlin code.
+            .addKeepRules("-keep class **.ImplKt { <methods>; }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String implClassName = pkg + ".classpath_lib_ext.Impl";
+    final String extraClassName = pkg + ".classpath_lib_ext.Extra";
+    compileResult.inspect(inspector -> {
+      ClassSubject impl = inspector.clazz(implClassName);
+      assertThat(impl, not(isPresent()));
+
+      ClassSubject extra = inspector.clazz(extraClassName);
+      assertThat(extra, isPresent());
+      assertThat(extra, not(isRenamed()));
+      // API entry is kept, hence the presence of Metadata.
+      KmClassSubject kmClass = extra.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Impl")));
+      // Can't build ClassSubject with Itf in classpath. Instead, check if the reference to Itf is
+      // not altered via descriptors.
+      List<String> superTypeDescriptors = kmClass.getSuperTypeDescriptors();
+      assertTrue(superTypeDescriptors.stream().noneMatch(supertype -> supertype.contains("Impl")));
+      assertTrue(superTypeDescriptors.stream().anyMatch(supertype -> supertype.contains("Itf")));
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/classpath_app";
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(baseLibJar, libJar)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".classpath_app.MainKt")
+        .assertSuccessWithOutputLines("Impl::foo");
+  }
+
+  @Test
+  public void testMetadataInClasspathType_renamed() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(baseLibJar)
+            .addProgramFiles(extLibJar)
+            // Keep the Extra class and its interface (which has the method).
+            .addKeepRules("-keep class **.Extra")
+            // Keep Super, but allow minification.
+            .addKeepRules("-keep,allowobfuscation class **.Impl")
+            // Keep the ImplKt extension method which requires metadata
+            // to be called with Kotlin syntax from other kotlin code.
+            .addKeepRules("-keep class **.ImplKt { <methods>; }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String implClassName = pkg + ".classpath_lib_ext.Impl";
+    final String extraClassName = pkg + ".classpath_lib_ext.Extra";
+    compileResult.inspect(inspector -> {
+      ClassSubject impl = inspector.clazz(implClassName);
+      assertThat(impl, isPresent());
+      assertThat(impl, isRenamed());
+
+      ClassSubject extra = inspector.clazz(extraClassName);
+      assertThat(extra, isPresent());
+      assertThat(extra, not(isRenamed()));
+      // API entry is kept, hence the presence of Metadata.
+      KmClassSubject kmClass = extra.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Impl")));
+      assertTrue(superTypes.stream().anyMatch(
+          supertype -> supertype.getFinalDescriptor().equals(impl.getFinalDescriptor())));
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/classpath_app";
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(baseLibJar, libJar)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".classpath_app.MainKt")
+        .assertSuccessWithOutputLines("Impl::foo");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
index e256e37..e7692bc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionTest.java
@@ -6,23 +6,20 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,15 +47,10 @@
   @BeforeClass
   public static void createLibJar() throws Exception {
     String extLibFolder = PKG_PREFIX + "/extension_lib";
-    extLibJar = getStaticTemp().newFile("ext_lib.jar").toPath();
-    ProcessResult processResult =
-        ToolHelper.runKotlinc(
-            null,
-            extLibJar,
-            null,
-            getKotlinFileInTest(extLibFolder, "B")
-        );
-    assertEquals(0, processResult.exitCode);
+    extLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(getKotlinFileInTest(extLibFolder, "B"))
+            .compile();
   }
 
   @Test
@@ -85,25 +77,28 @@
       assertThat(impl, isPresent());
       assertThat(impl, not(isRenamed()));
       // API entry is kept, hence the presence of Metadata.
-      DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
-      assertNotNull(metadata);
-      assertThat(metadata.toString(), not(containsString("Super")));
+      KmClassSubject kmClass = impl.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Super")));
     });
 
-    Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
-    compileResult.writeToZip(r8ProcessedLibZip);
+    Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/extension_app";
-    ProcessResult kotlinTestCompileResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
-            .addClasspathFiles(r8ProcessedLibZip)
+            .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/143687784): update to just .compile() once fixed.
-            .compileRaw();
-    // TODO(b/143687784): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".extension_app.MainKt")
+        .assertSuccessWithOutputLines("do stuff", "do stuff");
   }
 
   @Test
@@ -133,13 +128,16 @@
       assertThat(impl, isPresent());
       assertThat(impl, not(isRenamed()));
       // API entry is kept, hence the presence of Metadata.
-      DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
-      assertNotNull(metadata);
-      assertThat(metadata.toString(), not(containsString("Super")));
+      KmClassSubject kmClass = impl.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Super")));
+      assertTrue(superTypes.stream().anyMatch(
+          supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
     });
 
-    Path libJar = temp.newFile("lib.jar").toPath();
-    compileResult.writeToZip(libJar);
+    Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/extension_app";
     Path output =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParametertypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
similarity index 71%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParametertypeTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
index 1030339..3fedef1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParametertypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
@@ -9,27 +9,26 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class MetadataRenameInParametertypeTest extends KotlinMetadataTestBase {
+public class MetadataRenameInParameterTypeTest extends KotlinMetadataTestBase {
 
   private final TestParameters parameters;
 
@@ -39,33 +38,28 @@
         getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
   }
 
-  public MetadataRenameInParametertypeTest(
+  public MetadataRenameInParameterTypeTest(
       TestParameters parameters, KotlinTargetVersion targetVersion) {
     super(targetVersion);
     this.parameters = parameters;
   }
 
-  private static Path parameterLibJar;
+  private static Path parameterTypeLibJar;
 
   @BeforeClass
   public static void createLibJar() throws Exception {
-    String paramLibFolder = PKG_PREFIX + "/parametertype_lib";
-    parameterLibJar = getStaticTemp().newFile("param_lib.jar").toPath();
-    ProcessResult processResult =
-        ToolHelper.runKotlinc(
-            null,
-            parameterLibJar,
-            null,
-            getKotlinFileInTest(paramLibFolder, "lib")
-        );
-    assertEquals(0, processResult.exitCode);
+    String parameterTypeLibFolder = PKG_PREFIX + "/parametertype_lib";
+    parameterTypeLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(getKotlinFileInTest(parameterTypeLibFolder, "lib"))
+            .compile();
   }
 
   @Test
-  public void testMetadataInParameter_renamed() throws Exception {
+  public void testMetadataInParameterType_renamed() throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
-            .addProgramFiles(parameterLibJar)
+            .addProgramFiles(parameterTypeLibJar)
             // Keep non-private members of Impl
             .addKeepRules("-keep public class **.Impl { !private *; }")
             // Keep Itf, but allow minification.
@@ -84,14 +78,20 @@
       assertThat(impl, isPresent());
       assertThat(impl, not(isRenamed()));
       // API entry is kept, hence the presence of Metadata.
-      DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
-      assertNotNull(metadata);
+      KmClassSubject kmClass = impl.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Itf")));
+      assertTrue(superTypes.stream().anyMatch(
+          supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
       // TODO(b/70169921): should not refer to Itf
-      assertThat(metadata.toString(), containsString("Itf"));
+      List<ClassSubject> parameterTypes = kmClass.getParameterTypesInFunctions();
+      assertTrue(parameterTypes.stream().anyMatch(
+          parameterType -> parameterType.getOriginalDescriptor().contains("Itf")));
     });
 
-    Path libJar = temp.newFile("lib.jar").toPath();
-    compileResult.writeToZip(libJar);
+    Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/parametertype_app";
     ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
similarity index 71%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
index fe815d0..2e92333 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
@@ -9,27 +9,26 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class MetadataRenameInPropertyTest extends KotlinMetadataTestBase {
+public class MetadataRenameInPropertyTypeTest extends KotlinMetadataTestBase {
 
   private final TestParameters parameters;
 
@@ -39,33 +38,28 @@
         getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
   }
 
-  public MetadataRenameInPropertyTest(
+  public MetadataRenameInPropertyTypeTest(
       TestParameters parameters, KotlinTargetVersion targetVersion) {
     super(targetVersion);
     this.parameters = parameters;
   }
 
-  private static Path propertyLibJar;
+  private static Path propertyTypeLibJar;
 
   @BeforeClass
   public static void createLibJar() throws Exception {
-    String propertyLibFolder = PKG_PREFIX + "/propertytype_lib";
-    propertyLibJar = getStaticTemp().newFile("property_lib.jar").toPath();
-    ProcessResult processResult =
-        ToolHelper.runKotlinc(
-            null,
-            propertyLibJar,
-            null,
-            getKotlinFileInTest(propertyLibFolder, "lib")
-        );
-    assertEquals(0, processResult.exitCode);
+    String propertyTypeLibFolder = PKG_PREFIX + "/propertytype_lib";
+    propertyTypeLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(getKotlinFileInTest(propertyTypeLibFolder, "lib"))
+            .compile();
   }
 
   @Test
   public void testMetadataInProperty_renamed() throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
-            .addProgramFiles(propertyLibJar)
+            .addProgramFiles(propertyTypeLibJar)
             // Keep non-private members of Impl
             .addKeepRules("-keep public class **.Impl { !private *; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
@@ -82,14 +76,20 @@
       assertThat(impl, isPresent());
       assertThat(impl, not(isRenamed()));
       // API entry is kept, hence the presence of Metadata.
-      DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
-      assertNotNull(metadata);
+      KmClassSubject kmClass = impl.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Itf")));
+      assertTrue(superTypes.stream().anyMatch(
+          supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
       // TODO(b/70169921): should not refer to Itf
-      assertThat(metadata.toString(), containsString("Itf"));
+      List<ClassSubject> propertyReturnTypes = kmClass.getReturnTypesInProperties();
+      assertTrue(propertyReturnTypes.stream().anyMatch(
+          propertyType -> propertyType.getOriginalDescriptor().contains("Itf")));
     });
 
-    Path libJar = temp.newFile("lib.jar").toPath();
-    compileResult.writeToZip(libJar);
+    Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/propertytype_app";
     ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturntypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturntypeTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
index da6bf76..e7efcae 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturntypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
@@ -9,27 +9,26 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class MetadataRenameInReturntypeTest extends KotlinMetadataTestBase {
+public class MetadataRenameInReturnTypeTest extends KotlinMetadataTestBase {
   private final TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0} target: {1}")
@@ -38,33 +37,28 @@
         getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
   }
 
-  public MetadataRenameInReturntypeTest(
+  public MetadataRenameInReturnTypeTest(
       TestParameters parameters, KotlinTargetVersion targetVersion) {
     super(targetVersion);
     this.parameters = parameters;
   }
 
-  private static Path returntypeLibJar;
+  private static Path returnTypeLibJar;
 
   @BeforeClass
   public static void createLibJar() throws Exception {
-    String returntypeLibFolder = PKG_PREFIX + "/returntype_lib";
-    returntypeLibJar = getStaticTemp().newFile("returntype_lib.jar").toPath();
-    ProcessResult processResult =
-        ToolHelper.runKotlinc(
-            null,
-            returntypeLibJar,
-            null,
-            getKotlinFileInTest(returntypeLibFolder, "lib")
-        );
-    assertEquals(0, processResult.exitCode);
+    String returnTypeLibFolder = PKG_PREFIX + "/returntype_lib";
+    returnTypeLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(getKotlinFileInTest(returnTypeLibFolder, "lib"))
+            .compile();
   }
 
   @Test
-  public void testmetadataInReturnType_renamed() throws Exception {
+  public void testMetadataInReturnType_renamed() throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
-            .addProgramFiles(returntypeLibJar)
+            .addProgramFiles(returnTypeLibJar)
             // Keep non-private members of Impl
             .addKeepRules("-keep public class **.Impl { !private *; }")
             // Keep Itf, but allow minification.
@@ -83,14 +77,20 @@
       assertThat(impl, isPresent());
       assertThat(impl, not(isRenamed()));
       // API entry is kept, hence the presence of Metadata.
-      DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
-      assertNotNull(metadata);
+      KmClassSubject kmClass = impl.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Itf")));
+      assertTrue(superTypes.stream().anyMatch(
+          supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
       // TODO(b/70169921): should not refer to Itf
-      assertThat(metadata.toString(), containsString("Itf"));
+      List<ClassSubject> functionReturnTypes = kmClass.getReturnTypesInFunctions();
+      assertTrue(functionReturnTypes.stream().anyMatch(
+          returnType -> returnType.getOriginalDescriptor().contains("Itf")));
     });
 
-    Path libJar = temp.newFile("lib.jar").toPath();
-    compileResult.writeToZip(libJar);
+    Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/returntype_app";
     ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSuperTypeTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSuperTypeTest.java
index dc29f2c..4ad8e97 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSupertypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSuperTypeTest.java
@@ -6,29 +6,27 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class MetadataRenameInSupertypeTest extends KotlinMetadataTestBase {
+public class MetadataRenameInSuperTypeTest extends KotlinMetadataTestBase {
 
   private final TestParameters parameters;
 
@@ -38,34 +36,30 @@
         getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
   }
 
-  public MetadataRenameInSupertypeTest(
+  public MetadataRenameInSuperTypeTest(
       TestParameters parameters, KotlinTargetVersion targetVersion) {
     super(targetVersion);
     this.parameters = parameters;
   }
 
-  private static Path supertypeLibJar;
+  private static Path superTypeLibJar;
 
   @BeforeClass
   public static void createLibJar() throws Exception {
-    String supertypeLibFolder = PKG_PREFIX + "/supertype_lib";
-    supertypeLibJar = getStaticTemp().newFile("supertype_lib.jar").toPath();
-    ProcessResult processResult =
-        ToolHelper.runKotlinc(
-            null,
-            supertypeLibJar,
-            null,
-            getKotlinFileInTest(supertypeLibFolder, "impl"),
-            getKotlinFileInTest(supertypeLibFolder + "/internal", "itf")
-        );
-    assertEquals(0, processResult.exitCode);
+    String superTypeLibFolder = PKG_PREFIX + "/supertype_lib";
+    superTypeLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(
+                getKotlinFileInTest(superTypeLibFolder, "impl"),
+                getKotlinFileInTest(superTypeLibFolder + "/internal", "itf"))
+            .compile();
   }
 
   @Test
   public void b143687784_merged() throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
-            .addProgramFiles(supertypeLibJar)
+            .addProgramFiles(superTypeLibJar)
             // Keep non-private members except for ones in `internal` definitions.
             .addKeepRules("-keep public class !**.internal.**, * { !private *; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
@@ -81,25 +75,27 @@
       assertThat(impl, isPresent());
       assertThat(impl, not(isRenamed()));
       // API entry is kept, hence the presence of Metadata.
-      DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
-      assertNotNull(metadata);
-      assertThat(metadata.toString(), not(containsString("internal")));
-      assertThat(metadata.toString(), not(containsString("Itf")));
+      KmClassSubject kmClass = impl.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("internal")));
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Itf")));
     });
 
-    Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
-    compileResult.writeToZip(r8ProcessedLibZip);
+    Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/supertype_app";
     Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
-            .addClasspathFiles(r8ProcessedLibZip)
+            .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
             .setOutputPath(temp.newFolder().toPath())
             .compile();
 
     testForJvm()
-        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), pkg + ".supertype_app.MainKt")
         .assertSuccessWithOutputLines("Impl::foo", "Program::foo");
@@ -109,7 +105,7 @@
   public void b143687784_renamed() throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
-            .addProgramFiles(supertypeLibJar)
+            .addProgramFiles(superTypeLibJar)
             // Keep non-private members except for ones in `internal` definitions.
             .addKeepRules("-keep public class !**.internal.**, * { !private *; }")
             // Keep `internal` definitions, but allow minification.
@@ -128,15 +124,18 @@
       assertThat(impl, isPresent());
       assertThat(impl, not(isRenamed()));
       // API entry is kept, hence the presence of Metadata.
-      DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
-      assertNotNull(metadata);
-      assertThat(metadata.toString(), not(containsString("internal")));
-      assertThat(metadata.toString(), not(containsString("Itf")));
-      assertThat(metadata.toString(), containsString("a/a"));
+      KmClassSubject kmClass = impl.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("internal")));
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Itf")));
+      assertTrue(superTypes.stream().anyMatch(
+          supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
     });
 
-    Path libJar = temp.newFile("lib.jar").toPath();
-    compileResult.writeToZip(libJar);
+    Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/supertype_app";
     Path output =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 00b5606..35dd597 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -7,8 +7,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.R8TestRunResult;
@@ -16,6 +14,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.Collection;
@@ -61,11 +60,12 @@
     assertThat(clazz, isPresent());
     assertThat(clazz, not(isRenamed()));
     // Main class is kept, hence the presence of Metadata.
-    assertNotNull(retrieveMetadata(clazz.getDexClass()));
+    AnnotationSubject annotationSubject = clazz.annotation(METADATA_TYPE);
+    assertThat(annotationSubject, isPresent());
     ClassSubject impl1 = inspector.clazz(implementer1ClassName);
     assertThat(impl1, isPresent());
     assertThat(impl1, isRenamed());
     // All other classes can be renamed, hence the absence of Metadata;
-    assertNull(retrieveMetadata(impl1.getDexClass()));
+    assertThat(impl1.annotation(METADATA_TYPE), not(isPresent()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
new file mode 100644
index 0000000..3c1585a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
@@ -0,0 +1,11 @@
+// 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.kotlin.metadata.classpath_app
+
+import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.Extra
+import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.extension
+
+fun main() {
+  Extra().extension()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt
new file mode 100644
index 0000000..9df88be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_base/itf.kt
@@ -0,0 +1,8 @@
+// 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.kotlin.metadata.classpath_lib_base
+
+interface Itf {
+  fun foo()
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
new file mode 100644
index 0000000..975865b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
@@ -0,0 +1,18 @@
+// 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.kotlin.metadata.classpath_lib_ext
+
+import com.android.tools.r8.kotlin.metadata.classpath_lib_base.Itf
+
+open class Impl : Itf {
+  override fun foo() {
+    println("Impl::foo")
+  }
+}
+
+class Extra : Impl()
+
+fun Extra.extension() {
+  foo()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt
index a6fe9ab..df51d42 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt
@@ -13,7 +13,7 @@
   }
 }
 
-class B : Super() { }
+class B : Super()
 
 fun B.extension() {
   doStuff()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt
index 50e18d4..7fff074 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt
@@ -4,5 +4,5 @@
 package com.android.tools.r8.kotlin.metadata.supertype_lib.internal
 
 interface Itf {
-  fun foo();
+  fun foo()
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java b/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
index fdc195a..2815aed 100644
--- a/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
@@ -10,27 +10,15 @@
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.Collection;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
-@RunWith(Parameterized.class)
 public abstract class AbstractR8KotlinNamingTestBase extends AbstractR8KotlinTestBase {
 
   protected final boolean minification;
 
-  @Parameters(name = "target: {0}, allowAccessModification: {1}, minification: {2}")
-  public static Collection<Object[]> data() {
-    return buildParameters(
-        KotlinTargetVersion.values(), BooleanUtils.values(), BooleanUtils.values());
-  }
-
   AbstractR8KotlinNamingTestBase(
       KotlinTargetVersion kotlinTargetVersion,
       boolean allowAccessModification,
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index 43e8719..7828143 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.kotlin.TestKotlinClass;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -21,13 +22,24 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.stream.Collectors;
 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 KotlinIntrinsicsIdentifierTest extends AbstractR8KotlinNamingTestBase {
   private static final String FOLDER = "intrinsics_identifiers";
 
+  @Parameters(name = "target: {0}, allowAccessModification: {1}, minification: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        KotlinTargetVersion.values(), BooleanUtils.values(), BooleanUtils.values());
+  }
+
   public KotlinIntrinsicsIdentifierTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification, boolean minification) {
     super(targetVersion, allowAccessModification, minification);
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index a97372e..bcb02da 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -25,12 +25,12 @@
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index 5c01601..5737496 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -34,6 +33,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index dda4a3a..b19ed16 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -29,7 +29,6 @@
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -37,6 +36,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendEmptyInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendEmptyInterfaceTest.java
new file mode 100644
index 0000000..e16307b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendEmptyInterfaceTest.java
@@ -0,0 +1,118 @@
+// 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.naming.applymapping;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import 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;
+
+// This is a reproduction of b/144151805.
+@RunWith(Parameterized.class)
+public class ApplyMappingExtendEmptyInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ApplyMappingExtendEmptyInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    testForRuntime(parameters)
+        .addProgramClasses(
+            TestI.class,
+            TestA.class,
+            Main.class,
+            LibI.class,
+            LibI2.class,
+            LibI3.class,
+            Runner.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("injectTestA", "injectObject");
+  }
+
+  @Test
+  public void testInheritLibraryInterface()
+      throws CompilationFailedException, IOException, ExecutionException {
+    final R8TestCompileResult libCompileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(LibI.class, LibI2.class, LibI3.class, Runner.class)
+            .addKeepClassAndMembersRules(Runner.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(LibI.class, LibI2.class, LibI3.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestI.class, TestA.class, Main.class)
+        .addClasspathClasses(LibI.class, LibI2.class, LibI3.class, Runner.class)
+        .addKeepAllClassesRule()
+        .addApplyMapping(libCompileResult.getProguardMap())
+        .addRunClasspathFiles(libCompileResult.writeToZip())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("injectTestA", "injectObject");
+  }
+
+  public interface LibI {
+    void inject(Object object);
+  }
+
+  public interface LibI2 extends LibI {}
+
+  // Add an additional interface on top of LibI2 to ensure a class naming is generated here.
+  public interface LibI3 extends LibI2 {
+    void foo();
+  }
+
+  public static class Runner {
+
+    public static void foo(LibI libI) {
+      libI.inject(libI);
+    }
+  }
+
+  public interface TestI extends LibI3 {
+    void inject(TestA testA);
+  }
+
+  public static class TestA implements TestI {
+
+    @Override
+    public void inject(Object object) {
+      System.out.println("injectObject");
+    }
+
+    @Override
+    public void inject(TestA testA) {
+      System.out.println("injectTestA");
+    }
+
+    @Override
+    public void foo() {
+      throw new RuntimeException("Should never be called");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      final TestA testA = new TestA();
+      testA.inject(testA);
+      Runner.foo(testA);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendInterfaceTest.java
new file mode 100644
index 0000000..c70d49c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingExtendInterfaceTest.java
@@ -0,0 +1,99 @@
+// 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.naming.applymapping;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import 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;
+
+// This is a reproduction of b/144151805.
+@RunWith(Parameterized.class)
+public class ApplyMappingExtendInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ApplyMappingExtendInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    testForRuntime(parameters)
+        .addProgramClasses(TestI.class, TestA.class, Main.class, LibI.class, Runner.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("injectTestA", "injectObject");
+  }
+
+  @Test
+  public void testInheritLibraryInterface()
+      throws CompilationFailedException, IOException, ExecutionException {
+    final R8TestCompileResult libCompileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(LibI.class, Runner.class)
+            .addKeepClassAndMembersRules(Runner.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(LibI.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestI.class, TestA.class, Main.class)
+        .addClasspathClasses(LibI.class, Runner.class)
+        .addKeepAllClassesRule()
+        .addApplyMapping(libCompileResult.getProguardMap())
+        .addRunClasspathFiles(libCompileResult.writeToZip())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("injectTestA", "injectObject");
+  }
+
+  public interface LibI {
+    void inject(Object object);
+  }
+
+  public static class Runner {
+
+    public static void foo(LibI libI) {
+      libI.inject(libI);
+    }
+  }
+
+  public interface TestI extends LibI {
+    void inject(TestA testA);
+  }
+
+  public static class TestA implements TestI {
+
+    @Override
+    public void inject(Object object) {
+      System.out.println("injectObject");
+    }
+
+    @Override
+    public void inject(TestA testA) {
+      System.out.println("injectTestA");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      final TestA testA = new TestA();
+      testA.inject(testA);
+      Runner.foo(testA);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
index 63c6161..5c1d7a8 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -71,10 +70,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    // TODO(b/145187573): Update to check the full access control once possible.
-    assertEquals(
-        inSameNest,
-        NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index a487f40..bd6dbed 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -73,10 +72,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    // TODO(b/145187573): Update to check the full access control once possible.
-    assertEquals(
-        inSameNest,
-        NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @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 59267b2..69cdad5 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
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -73,10 +72,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    // TODO(b/145187573): Update to check the full access control once possible.
-    assertEquals(
-        inSameNest,
-        NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index 68cc59e..d6343fc 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -76,10 +75,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    // TODO(b/145187573): Update to check the full access control once possible.
-    assertEquals(
-        inSameNest,
-        NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @Test
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
new file mode 100644
index 0000000..a997622
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
@@ -0,0 +1,108 @@
+// 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.access;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SelfVirtualMethodAccessTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::bar");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SelfVirtualMethodAccessTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public Collection<Class<?>> getClasses() {
+    return ImmutableList.of(Main.class);
+  }
+
+  public Collection<byte[]> getTransformedClasses() throws Exception {
+    return ImmutableList.of(
+        transformer(A.class).setPrivate(A.class.getDeclaredMethod("bar")).transform());
+  }
+
+  @Test
+  public void testResolutionAccess() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
+            Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexProgramClass aClass =
+        appInfo.definitionFor(buildType(A.class, appInfo.dexItemFactory())).asProgramClass();
+    DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+    assertTrue(resolutionResult.isAccessibleFrom(aClass, appInfo));
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .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()));
+              }
+            });
+  }
+
+  static class A {
+    /* will be private */ void bar() {
+      System.out.println("A::bar");
+    }
+
+    public void foo() {
+      // Virtual invoke to private method.
+      bar();
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
new file mode 100644
index 0000000..e517531
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
@@ -0,0 +1,105 @@
+// 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.access.indirectfield;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.resolution.access.indirectfield.pkg.C;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IndirectFieldAccessTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("42");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IndirectFieldAccessTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public List<Class<?>> getClasses() {
+    return ImmutableList.of(Main.class, A.class, B.class, C.class);
+  }
+
+  @Test
+  public void testResolutionAccess() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(readClasses(getClasses()), Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexProgramClass cClass =
+        appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
+    DexField f =
+        buildField(
+            // Reflecting on B.class.getField("f") will give A.f, so manually create the reference.
+            Reference.field(Reference.classFromClass(B.class), "f", Reference.INT),
+            appInfo.dexItemFactory());
+    DexClass initialResolutionHolder = appInfo.definitionFor(f.holder);
+    DexEncodedField resolutionTarget = appInfo.resolveField(f);
+    // TODO(b/145723539): Test access via the resolution result once possible.
+    assertTrue(
+        AccessControl.isFieldAccessible(
+            resolutionTarget, initialResolutionHolder, cClass, appInfo));
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getClasses())
+        .run(parameters.getRuntime(), Main.class)
+        .disassemble()
+        .apply(this::checkExpectedResult);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkExpectedResult);
+  }
+
+  private void checkExpectedResult(TestRunResult<?> result) {
+    result.assertSuccessWithOutput(EXPECTED);
+  }
+
+  /* non-public */ static class A {
+    public int f = 42;
+  }
+
+  public static class B extends A {
+    // Intentionally emtpy.
+    // Provides access to A.f from outside this package, eg, from pkg.C::bar().
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java
new file mode 100644
index 0000000..9094062
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java
@@ -0,0 +1,12 @@
+// 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.access.indirectfield.pkg;
+
+import com.android.tools.r8.resolution.access.indirectfield.IndirectFieldAccessTest.B;
+
+public class C {
+  public void bar() {
+    System.out.println(new B().f);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
new file mode 100644
index 0000000..1d75a8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
@@ -0,0 +1,108 @@
+// 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.access.indirectmethod;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.access.indirectmethod.pkg.C;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IndirectMethodAccessTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IndirectMethodAccessTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public List<Class<?>> getClasses() {
+    return ImmutableList.of(Main.class, A.class, C.class);
+  }
+
+  private List<byte[]> getTransforms() throws IOException {
+    return ImmutableList.of(
+        // Compilation with javac will generate a synthetic bridge for foo. Remove it.
+        transformer(B.class)
+            .removeMethods((access, name, descriptor, signature, exceptions) -> name.equals("foo"))
+            .transform());
+  }
+
+  @Test
+  public void testResolutionAccess() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(getClasses()).addClassProgramData(getTransforms()).build(), Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexProgramClass cClass =
+        appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
+    DexMethod bar = buildMethod(B.class.getMethod("foo"), appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+    assertTrue(resolutionResult.isAccessibleForVirtualDispatchFrom(cClass, appInfo));
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransforms())
+        .run(parameters.getRuntime(), Main.class)
+        .disassemble()
+        .apply(this::checkExpectedResult);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransforms())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkExpectedResult);
+  }
+
+  private void checkExpectedResult(TestRunResult<?> result) {
+    result.assertSuccessWithOutput(EXPECTED);
+  }
+
+  /* non-public */ static class A {
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  public static class B extends A {
+    // Intentionally emtpy.
+    // Provides access to A.foo from outside this package, eg, from pkg.C::bar().
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java
new file mode 100644
index 0000000..2ff8db8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java
@@ -0,0 +1,12 @@
+// 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.access.indirectmethod.pkg;
+
+import com.android.tools.r8.resolution.access.indirectmethod.IndirectMethodAccessTest.B;
+
+public class C {
+  public void bar() {
+    new B().foo();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index cba0f8b..d81c771 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -4,22 +4,34 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.Collectors.toSingle;
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.containsInlinePosition;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
-import java.util.List;
+import java.nio.file.Path;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -40,15 +52,21 @@
     this.parameters = parameters;
   }
 
+  private static Function<TestRuntime, Path> compilationResults =
+      memoizeFunction(KotlinInlineFunctionRetraceTest::compileKotlinCode);
+
+  private static Path compileKotlinCode(TestRuntime runtime) throws IOException {
+    CfRuntime cfRuntime = runtime.isCf() ? runtime.asCf() : TestRuntime.getCheckedInJdk9();
+    return kotlinc(cfRuntime, getStaticTemp(), KOTLINC, KotlinTargetVersion.JAVA_8)
+        .addSourceFiles(
+            getFilesInTestFolderRelativeToClass(KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+        .compile();
+  }
+
   @Test
   public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
     testForRuntime(parameters)
-        .addProgramFiles(
-            kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
-                .addSourceFiles(
-                    getFilesInTestFolderRelativeToClass(
-                        KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
-                .compile())
+        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
         .addRunClasspathFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar()))
         .run(parameters.getRuntime(), "retrace.MainKt")
         .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
@@ -59,79 +77,128 @@
   public void testRetraceKotlinInlineStaticFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainKt";
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(
-                kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
-                    .addSourceFiles(
-                        getFilesInTestFolderRelativeToClass(
-                            KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
-                    .compile())
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-            .addKeepAttributes("SourceFile", "LineNumberTable")
-            .setMode(CompilationMode.RELEASE)
-            .addKeepMainRule(main)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), main)
-            .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
-            .assertFailureWithErrorThatMatches(containsString("at retrace.MainKt.main(Main.kt:2)"));
-    List<String> retrace = result.retrace();
-    // TODO(b/141817471): Change the tracing information when solved.
-    // assertThat(retrace.get(1), containsString("at retrace.MainKt.main(Main.kt:15)"));
+    testForR8(parameters.getBackend())
+        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepAttributes("SourceFile", "LineNumberTable")
+        .setMode(CompilationMode.RELEASE)
+        .addKeepMainRule(main)
+        .addOptionsModification(
+            internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15));
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+            });
   }
 
   @Test
   public void testRetraceKotlinInlineInstanceFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainInstanceKt";
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(
-                kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
-                    .addSourceFiles(
-                        getFilesInTestFolderRelativeToClass(
-                            KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
-                    .compile())
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-            .addKeepAttributes("SourceFile", "LineNumberTable")
-            .setMode(CompilationMode.RELEASE)
-            .addKeepMainRule(main)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), main)
-            .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
-            .assertFailureWithErrorThatMatches(
-                containsString("at retrace.MainInstanceKt.main(MainInstance.kt:2)"));
-    List<String> retrace = result.retrace();
-    // TODO(b/141817471): Change the tracing information when solved.
-    // assertThat(retrace.get(1), containsString("at
-    // retrace.MainInstanceKt.main(MainInstance.kt:13)"));
+    testForR8(parameters.getBackend())
+        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepAttributes("SourceFile", "LineNumberTable")
+        .setMode(CompilationMode.RELEASE)
+        .addKeepMainRule(main)
+        .addOptionsModification(
+            internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          "retrace.InlineFunction", "inlineExceptionInstance", 2, 15),
+                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13));
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+            });
   }
 
   @Test
   public void testRetraceKotlinNestedInlineFunction()
       throws ExecutionException, CompilationFailedException, IOException {
-    // TODO(b/141817471): Change the tracing information when solved.
-    int lineNumber = parameters.isCfRuntime() ? 4 : 3;
     String main = "retrace.MainNestedKt";
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(
-                kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
-                    .addSourceFiles(
-                        getFilesInTestFolderRelativeToClass(
-                            KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
-                    .compile())
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-            .addKeepAttributes("SourceFile", "LineNumberTable")
-            .setMode(CompilationMode.RELEASE)
-            .addKeepMainRule(main)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), main)
-            .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
-            .assertFailureWithErrorThatMatches(
-                containsString("at retrace.MainNestedKt.main(MainNested.kt:" + lineNumber + ")"));
-    List<String> retrace = result.retrace();
-    // TODO(b/141817471): Change the tracing information when solved.
-    // assertThat(retrace.get(1), containsString("at retrace.MainNestedKt.main(MainNested.kt:19)"));
+    testForR8(parameters.getBackend())
+        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepAttributes("SourceFile", "LineNumberTable")
+        .setMode(CompilationMode.RELEASE)
+        .addKeepMainRule(main)
+        .addOptionsModification(
+            internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          "retrace.InlineFunctionKt", "inlineExceptionStatic", 3, 8),
+                      InlinePosition.create(
+                          "retrace.NestedInlineFunctionKt", "nestedInline", 3, 10),
+                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19));
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+            });
+  }
+
+  @Test
+  public void testRetraceKotlinNestedInlineFunctionOnFirstLine()
+      throws ExecutionException, CompilationFailedException, IOException {
+    String main = "retrace.MainNestedFirstLineKt";
+    testForR8(parameters.getBackend())
+        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepAttributes("SourceFile", "LineNumberTable")
+        .setMode(CompilationMode.RELEASE)
+        .addKeepMainRule(main)
+        .addOptionsModification(
+            internalOptions -> internalOptions.enableSourceDebugExtensionRewriter = true)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+                      InlinePosition.create(
+                          "retrace.NestedInlineFunctionKt", "nestedInlineOnFirstLine", 2, 15),
+                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20));
+              checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+            });
+  }
+
+  private void checkInlineInformation(
+      StackTrace stackTrace,
+      CodeInspector codeInspector,
+      MethodSubject mainSubject,
+      InlinePosition inlineStack) {
+    assertThat(mainSubject, isPresent());
+    RetraceMethodResult retraceResult =
+        mainSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isThrow)
+            .collect(toSingle())
+            .retracePosition(codeInspector.retrace());
+    assertThat(retraceResult, isInlineFrame());
+    assertThat(retraceResult, isInlineStack(inlineStack));
+    assertThat(stackTrace, containsInlinePosition(inlineStack));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
new file mode 100644
index 0000000..bb69fc3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
@@ -0,0 +1,750 @@
+// 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.InlineFileNameStackTrace;
+import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceRegularExpressionTests extends TestBase {
+
+  private static final String DEFAULT_REGULAR_EXPRESSION =
+      "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)|(?:(?:.*?[:\"]\\s+)?%c(?::.*)?)";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RetraceRegularExpressionTests(TestParameters parameters) {}
+
+  @Test
+  public void ensureNotMatchingOnLiteral() {
+    runRetraceTest(
+        "foo\\%c",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("foocom.android.tools.r8.a");
+          }
+
+          @Override
+          public String mapping() {
+            return "com.android.tools.r8.R8 -> com.android.tools.r8.a:";
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("foocom.android.tools.r8.a");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchMultipleTypeNamesOnLine() {
+    runRetraceTest(
+        "%c\\s%c\\s%c",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.a.a b.b.b c.c.c");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "AA.AA.AA -> a.a.a:", "BB.BB.BB -> b.b.b:", "CC.CC.CC -> c.c.c:");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("AA.AA.AA BB.BB.BB CC.CC.CC");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchMultipleSlashNamesOnLine() {
+    runRetraceTest(
+        "%C\\s%C\\s%C",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a/a/a b/b/b c/c/c");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("AA.AA -> a.a.a:", "BB.BB -> b.b.b:", "CC.CC -> c.c.c:");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("AA/AA BB/BB CC/CC");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchMethodNameInNoContext() {
+    runRetraceTest(
+        "a.b.c.%m",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", "  void foo() -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("a.b.c.a");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchMethodNameInContext() {
+    runRetraceTest(
+        "%c.%m",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", "  void foo() -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.foo");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchUnknownMethodNameInContext() {
+    runRetraceTest(
+        "%c.%m",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", "  void foo() -> b");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.a");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchQualifiedMethodInTrace() {
+    runRetraceTest(DEFAULT_REGULAR_EXPRESSION, new InlineFileNameStackTrace());
+  }
+
+  @Test
+  public void matchFieldNameInNoContext() {
+    runRetraceTest(
+        "a.b.c.%f",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", "  int foo -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("a.b.c.a");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchFieldNameInContext() {
+    runRetraceTest(
+        "%c.%f",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", "  int foo -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.foo");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchUnknownFieldNameInContext() {
+    runRetraceTest(
+        "%c.%f",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", "  boolean foo -> b");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.a");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchQualifiedFieldInTrace() {
+    runRetraceTest(
+        "%c\\.%f\\(%s\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return Collections.singletonList("a.b.c.d(Main.dummy)");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return Collections.singletonList("foo.Bar$Baz.baz(Bar.dummy)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.naming.retrace.Main -> a.b.c:",
+                "    int foo.Bar$Baz.baz -> d");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchSourceFileInContext() {
+    runRetraceTest(
+        "%c\\(%s\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c(SourceFile)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", "  boolean foo -> b");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8(R8.java)");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchSourceFileInNoContext() {
+    runRetraceTest(
+        "%c\\(%s\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.d(SourceFile)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", "  boolean foo -> b");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("a.b.d(d.java)");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void testLineNumberInMethodContext() {
+    runRetraceTest(
+        "%c\\.%m\\(%l\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a(3)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.R8 -> a.b.c:", "  3:3:boolean foo():7 -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.foo(7)");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void testNotFoundLineNumberInMethodContext() {
+    runRetraceTest(
+        "%c\\.%m\\(%l\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a()");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.R8 -> a.b.c:", "  3:3:boolean foo():7 -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("a.b.c.a()");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void testNotFoundLineNumberInNoLineNumberMappingMethodContext() {
+    runRetraceTest(
+        "%c\\.%m\\(%l\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a(4)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines("com.android.tools.r8.R8 -> a.b.c:", "  boolean foo() -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.foo(4)");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void testPruningByLineNumber() {
+    runRetraceTest(
+        "%c\\.%m\\(%l\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a(3)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.R8 -> a.b.c:",
+                "  3:3:boolean foo():7 -> a",
+                "  4:4:boolean bar(int):8 -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.foo(7)");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchOnType() {
+    runRetraceTest(
+        "%t",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("void", "a.a.a[]", "a.a.a[][][]");
+          }
+
+          @Override
+          public String mapping() {
+            return "";
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("void", "a.a.a[]", "a.a.a[][][]");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchOnFieldOrReturnType() {
+    runRetraceTest(
+        "%t %c.%m",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("void a.b.c.a", "a.a.a[] a.b.c.b");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.D8 -> a.a.a:",
+                "com.android.tools.r8.R8 -> a.b.c:",
+                "  void foo() -> a",
+                "  com.android.tools.r8.D8[] bar() -> b");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of(
+                "void com.android.tools.r8.R8.foo",
+                "com.android.tools.r8.D8[] com.android.tools.r8.R8.bar");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void useReturnTypeToNarrowMethodMatches() {
+    runRetraceTest(
+        "%t %c.%m",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("void a.b.c.a", "a.a.a[] a.b.c.b");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.D8 -> a.a.a:",
+                "com.android.tools.r8.R8 -> a.b.c:",
+                "  void foo() -> a",
+                "  int foo(int) -> a",
+                "  com.android.tools.r8.D8[] bar() -> b",
+                "  com.android.tools.r8.D8 bar(int) -> b");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of(
+                "void com.android.tools.r8.R8.foo",
+                "com.android.tools.r8.D8[] com.android.tools.r8.R8.bar");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void returnTypeCanMatchVoid() {
+    runRetraceTest(
+        "%t",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("void");
+          }
+
+          @Override
+          public String mapping() {
+            return "";
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("void");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void testArguments() {
+    runRetraceTest(
+        "%c.%m\\(%a\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a(int,a.a.a[],boolean)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.D8 -> a.a.a:",
+                "com.android.tools.r8.R8 -> a.b.c:",
+                "  void foo(int,com.android.tools.r8.D8[],boolean) -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of(
+                "com.android.tools.r8.R8.foo(int,com.android.tools.r8.D8[],boolean)");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void testNoArguments() {
+    runRetraceTest(
+        "%c.%m\\(%a\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a()");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.D8 -> a.a.a:",
+                "com.android.tools.r8.R8 -> a.b.c:",
+                "  void foo() -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.foo()");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void testPruningOfMethodsByFormals() {
+    runRetraceTest(
+        "%c.%m\\(%a\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a(a.a.a)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.D8 -> a.a.a:",
+                "com.android.tools.r8.R8 -> a.b.c:",
+                "  void foo() -> a",
+                "  void bar(com.android.tools.r8.D8) -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.bar(com.android.tools.r8.D8)");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void matchOnSimpleStackTrace() {
+    runRetraceTest(
+        DEFAULT_REGULAR_EXPRESSION,
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of(
+                "com.android.tools.r8.a: foo bar baz",
+                "  at com.android.tools.r8.b.a(SourceFile)",
+                "  at a.c.a.b(SourceFile)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.joinLines(
+                "com.android.tools.r8.R8 -> com.android.tools.r8.a:",
+                "com.android.tools.r8.Bar -> com.android.tools.r8.b:",
+                "  void foo() -> a",
+                "com.android.tools.r8.Baz -> a.c.a:",
+                "  void bar() -> b");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of(
+                "com.android.tools.r8.R8: foo bar baz",
+                "  at com.android.tools.r8.Bar.foo(Bar.java)",
+                "  at com.android.tools.r8.Baz.bar(Baz.java)");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  private TestDiagnosticMessagesImpl runRetraceTest(
+      String regularExpression, StackTraceForTest stackTraceForTest) {
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    RetraceCommand retraceCommand =
+        RetraceCommand.builder(diagnosticsHandler)
+            .setProguardMapProducer(stackTraceForTest::mapping)
+            .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
+            .setRetracedStackTraceConsumer(
+                retraced -> {
+                  assertEquals(stackTraceForTest.retracedStackTrace(), retraced);
+                })
+            .setRegularExpression(regularExpression)
+            .build();
+    Retrace.run(retraceCommand);
+    return diagnosticsHandler;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/kt/MainNestedFirstLine.kt b/src/test/java/com/android/tools/r8/retrace/kt/MainNestedFirstLine.kt
new file mode 100644
index 0000000..6c34142
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/kt/MainNestedFirstLine.kt
@@ -0,0 +1,16 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package retrace
+
+// Some spaces to better see if retrace is working as expected.
+
+
+fun main(args: Array<String>) {
+  nestedInlineOnFirstLine {
+    throw Exception("Never get's here")
+  }
+}
+
+
+
diff --git a/src/test/java/com/android/tools/r8/retrace/kt/NestedInlineFunction.kt b/src/test/java/com/android/tools/r8/retrace/kt/NestedInlineFunction.kt
index d8e4736..3fdedb3 100644
--- a/src/test/java/com/android/tools/r8/retrace/kt/NestedInlineFunction.kt
+++ b/src/test/java/com/android/tools/r8/retrace/kt/NestedInlineFunction.kt
@@ -3,9 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package retrace
 
+// Some space to distinguish line number with Inlinefunction numbers.
+
 inline fun nestedInline(f: () -> Unit) {
   println("in nestedInline")
   inlineExceptionStatic(f)
   println("will never be printed")
 }
 
+inline fun nestedInlineOnFirstLine(f: () -> Unit) {
+  inlineExceptionStatic(f)
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 94c6be6..8443f06 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -169,11 +169,7 @@
     assertTrue(clazz.isPresent());
 
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("typeToString"));
-    if (parameters.isCfRuntime()) {
-      assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithToString"));
-    } else {
-      assertToStringReplacedWithConst(clazz.uniqueMethodWithName("valueWithToString"), "one");
-    }
+    assertToStringReplacedWithConst(clazz.uniqueMethodWithName("valueWithToString"), "one");
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithoutToString"));
 
     if (enableOptimization) {
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 3cd46e12..eebb7d5 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -21,6 +20,7 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 48c6dea..ee8629f 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.shaking.forceproguardcompatibility.TestMain.MentionedClass;
@@ -40,6 +39,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
index 8f19ef8..f2f6731 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
@@ -11,13 +11,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
index f62e21b..f618e93 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
@@ -97,7 +97,7 @@
           localVariableTable.get(0),
           0,
           "this",
-          classSubject.asFoundClassSubject().asTypeSybject(),
+          classSubject.asFoundClassSubject().asTypeSubject(),
           null);
       checkLocalVariable(
           localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("int"), null);
@@ -121,7 +121,7 @@
           localVariableTable.get(0),
           0,
           "this",
-          classSubject.asFoundClassSubject().asTypeSybject(),
+          classSubject.asFoundClassSubject().asTypeSubject(),
           null);
       checkLocalVariable(
           localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("long"), null);
@@ -141,7 +141,7 @@
           localVariableTable.get(0),
           0,
           "this",
-          classSubject.asFoundClassSubject().asTypeSybject(),
+          classSubject.asFoundClassSubject().asTypeSubject(),
           null);
       checkLocalVariable(
           localVariableTable.get(1),
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
index d858322..c92d7e7 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
@@ -73,7 +73,7 @@
           localVariableTable.get(0),
           0,
           "this",
-          classSubject.asFoundClassSubject().asTypeSybject(),
+          classSubject.asFoundClassSubject().asTypeSubject(),
           null);
       checkLocalVariable(
           localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("int"), null);
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
index a5fbc59..18188ff 100644
--- a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
@@ -66,16 +66,7 @@
     for (Class<?> nonEscapingClass : nonEscapingClasses) {
       ClassSubject classSubject = inspector.clazz(nonEscapingClass);
       assertThat(classSubject, isPresent());
-
-      // TODO(b/142772856): None of the non-escaping classes should have a toString() method. It is
-      //  a requirement that the instance initializers are considered trivial for this to work,
-      //  though, even when they have a side effect (as long as the receiver does not escape via the
-      //  side effecting instruction).
-      if (nonEscapingClass == DoesNotEscapeWithSubThatDoesNotOverrideSub.class) {
-        assertThat(classSubject.uniqueMethodWithName("toString"), not(isPresent()));
-      } else {
-        assertThat(classSubject.uniqueMethodWithName("toString"), isPresent());
-      }
+      assertThat(classSubject.uniqueMethodWithName("toString"), not(isPresent()));
     }
   }
 
@@ -135,8 +126,6 @@
   @NeverClassInline
   static class DoesNotEscape {
 
-    // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
-    //  side effect.
     DoesNotEscape() {
       // Side effect to ensure that the constructor is not removed from main().
       System.out.print("");
@@ -151,8 +140,6 @@
   @NeverClassInline
   static class DoesNotEscapeWithSubThatDoesNotOverride {
 
-    // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
-    //  side effect.
     DoesNotEscapeWithSubThatDoesNotOverride() {
       // Side effect to ensure that the constructor is not removed from main().
       System.out.print("");
@@ -168,8 +155,6 @@
   static class DoesNotEscapeWithSubThatDoesNotOverrideSub
       extends DoesNotEscapeWithSubThatDoesNotOverride {
 
-    // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
-    //  side effect.
     DoesNotEscapeWithSubThatDoesNotOverrideSub() {
       // Side effect to ensure that the constructor is not removed from main().
       System.out.print("");
@@ -179,8 +164,6 @@
   @NeverClassInline
   static class DoesNotEscapeWithSubThatOverrides {
 
-    // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
-    //  side effect.
     DoesNotEscapeWithSubThatOverrides() {
       // Side effect to ensure that the constructor is not removed from main().
       System.out.print("");
@@ -195,8 +178,6 @@
   @NeverClassInline
   static class DoesNotEscapeWithSubThatOverridesSub extends DoesNotEscapeWithSubThatOverrides {
 
-    // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
-    //  side effect.
     DoesNotEscapeWithSubThatOverridesSub() {
       // Side effect to ensure that the constructor is not removed from main().
       System.out.print("");
@@ -215,8 +196,6 @@
   @NeverClassInline
   static class DoesNotEscapeWithSubThatOverridesAndEscapes {
 
-    // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
-    //  side effect.
     DoesNotEscapeWithSubThatOverridesAndEscapes() {
       // Side effect to ensure that the constructor is not removed from main().
       System.out.print("");
@@ -232,8 +211,6 @@
   static class DoesNotEscapeWithSubThatOverridesAndEscapesSub
       extends DoesNotEscapeWithSubThatOverridesAndEscapes {
 
-    // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
-    //  side effect.
     DoesNotEscapeWithSubThatOverridesAndEscapesSub() {
       // Side effect to ensure that the constructor is not removed from main().
       System.out.print("");
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/C.java b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
index e547e0b..f402864 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/C.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
@@ -6,9 +6,7 @@
 
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
 
-@NeverMerge
 public class C {
 
   private static int i;
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index 2da0f7d..382ee13 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -14,7 +14,8 @@
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -25,22 +26,23 @@
 
 @RunWith(Parameterized.class)
 public class ForceInlineTest extends TestBase {
-  private Backend backend;
 
-  @Parameterized.Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  private TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public ForceInlineTest(Backend backend) {
-    this.backend = backend;
+  public ForceInlineTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   private CodeInspector runTest(List<String> proguardConfiguration) throws Exception {
-    return testForR8(backend)
+    return testForR8(parameters.getBackend())
         .addProgramClasses(Main.class, A.class, B.class, C.class)
         .addKeepRules(proguardConfiguration)
-        .assumeAllMethodsMayHaveSideEffects()
+        .enableProguardTestOptions()
         .compile()
         .inspector();
   }
@@ -61,7 +63,7 @@
     ClassSubject classMain = inspector.clazz(Main.class);
     assertThat(classA, isPresent());
     assertThat(classB, isPresent());
-    assertThat(classC, isPresent());
+    assertThat(classC, not(isPresent()));
     assertThat(classMain, isPresent());
 
     // By default A.m *will not* be inlined (called several times and not small).
@@ -92,7 +94,7 @@
     ClassSubject classMain = inspector.clazz(Main.class);
     assertThat(classA, isPresent());
     assertThat(classB, isPresent());
-    assertThat(classC, isPresent());
+    assertThat(classC, not(isPresent()));
     assertThat(classMain, isPresent());
 
     // Compared to the default method is no longer inlined.
@@ -114,16 +116,11 @@
                 "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
                 "-dontobfuscate"));
 
-    ClassSubject classA = inspector.clazz(A.class);
-    ClassSubject classB = inspector.clazz(B.class);
-    ClassSubject classC = inspector.clazz(C.class);
-    ClassSubject classMain = inspector.clazz(Main.class);
-
     // Compared to the default m is now inlined and method still is, so classes A and B are gone.
-    assertThat(classA, not(isPresent()));
-    assertThat(classB, not(isPresent()));
-    assertThat(classC, isPresent());
-    assertThat(classMain, isPresent());
+    assertThat(inspector.clazz(A.class), not(isPresent()));
+    assertThat(inspector.clazz(B.class), not(isPresent()));
+    assertThat(inspector.clazz(C.class), not(isPresent()));
+    assertThat(inspector.clazz(Main.class), isPresent());
   }
 
   @Test
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 09faf46..461323d 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -134,7 +134,7 @@
     return this;
   }
 
-  /** Base addtion of a transformer on methods. */
+  /** Base addition of a transformer on methods. */
   public ClassFileTransformer addMethodTransformer(MethodTransformer transformer) {
     methodTransformers.add(transformer);
     return this;
@@ -263,6 +263,24 @@
         });
   }
 
+  @FunctionalInterface
+  public interface MethodPredicate {
+    boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
+  }
+
+  public ClassFileTransformer removeMethods(MethodPredicate predicate) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            return predicate.test(access, name, descriptor, signature, exceptions)
+                ? null
+                : super.visitMethod(access, name, descriptor, signature, exceptions);
+          }
+        });
+  }
+
   /** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
   @FunctionalInterface
   public interface MethodInsnTransform {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 8e2f554..e665dcf 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -137,4 +137,9 @@
   public String getFinalSignatureAttribute() {
     return null;
   }
+
+  @Override
+  public KmClassSubject getKmClass() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
new file mode 100644
index 0000000..201f644
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.kotlin.Kotlin;
+import kotlinx.metadata.KmClass;
+
+public class AbsentKmClassSubject extends KmClassSubject {
+
+  @Override
+  public DexClass getDexClass() {
+    return null;
+  }
+
+  @Override
+  public KmClass getKmClass(Kotlin kotlin) {
+    return null;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
index 0a3263a..6273334 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
@@ -11,10 +11,12 @@
 class CfInstructionIterator implements InstructionIterator {
 
   private final CodeInspector codeInspector;
+  private final MethodSubject method;
   private final Iterator<CfInstruction> iterator;
 
   CfInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
     this.codeInspector = codeInspector;
+    this.method = method;
     assert method.isPresent();
     Code code = method.getMethod().getCode();
     assert code != null && code.isCfCode();
@@ -28,6 +30,6 @@
 
   @Override
   public InstructionSubject next() {
-    return codeInspector.createInstructionSubject(iterator.next());
+    return codeInspector.createInstructionSubject(iterator.next(), method);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index d7ea3ae..a9ea1b7 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -40,10 +40,13 @@
 import org.objectweb.asm.Opcodes;
 
 public class CfInstructionSubject implements InstructionSubject {
-  protected final CfInstruction instruction;
 
-  public CfInstructionSubject(CfInstruction instruction) {
+  protected final CfInstruction instruction;
+  private final MethodSubject method;
+
+  public CfInstructionSubject(CfInstruction instruction, MethodSubject method) {
     this.instruction = instruction;
+    this.method = method;
   }
 
   @Override
@@ -330,6 +333,7 @@
     return instruction instanceof CfArrayStore;
   }
 
+
   @Override
   public int size() {
     // TODO(b/122302789): CfInstruction#getSize()
@@ -343,6 +347,11 @@
   }
 
   @Override
+  public MethodSubject getMethodSubject() {
+    return method;
+  }
+
+  @Override
   public boolean equals(Object other) {
     return other instanceof CfInstructionSubject
         && instruction.equals(((CfInstructionSubject) other).instruction);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 8b8f519..705337c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -166,4 +166,6 @@
   public abstract String getOriginalSignatureAttribute();
 
   public abstract String getFinalSignatureAttribute();
+
+  public abstract KmClassSubject getKmClass();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 2bc1dd4..4aec544 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -37,6 +37,8 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceBase;
+import com.android.tools.r8.retrace.RetraceBaseImpl;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -70,6 +72,10 @@
   public static MethodSignature MAIN =
       new MethodSignature("main", "void", new String[] {"java.lang.String[]"});
 
+  public CodeInspector(String path) throws IOException, ExecutionException {
+    this(Paths.get(path));
+  }
+
   public CodeInspector(Path file, String mappingFile) throws IOException, ExecutionException {
     this(Collections.singletonList(file), mappingFile, null);
   }
@@ -331,31 +337,31 @@
     return originalTypeName != null ? originalTypeName : minifiedTypeName;
   }
 
-  InstructionSubject createInstructionSubject(Instruction instruction) {
-    DexInstructionSubject dexInst = new DexInstructionSubject(instruction);
+  InstructionSubject createInstructionSubject(Instruction instruction, MethodSubject method) {
+    DexInstructionSubject dexInst = new DexInstructionSubject(instruction, method);
     if (dexInst.isInvoke()) {
-      return new InvokeDexInstructionSubject(this, instruction);
+      return new InvokeDexInstructionSubject(this, instruction, method);
     } else if (dexInst.isFieldAccess()) {
-      return new FieldAccessDexInstructionSubject(this, instruction);
+      return new FieldAccessDexInstructionSubject(this, instruction, method);
     } else if (dexInst.isNewInstance()) {
-      return new NewInstanceDexInstructionSubject(instruction);
+      return new NewInstanceDexInstructionSubject(instruction, method);
     } else if (dexInst.isConstString(JumboStringMode.ALLOW)) {
-      return new ConstStringDexInstructionSubject(instruction);
+      return new ConstStringDexInstructionSubject(instruction, method);
     } else {
       return dexInst;
     }
   }
 
-  InstructionSubject createInstructionSubject(CfInstruction instruction) {
-    CfInstructionSubject cfInst = new CfInstructionSubject(instruction);
+  InstructionSubject createInstructionSubject(CfInstruction instruction, MethodSubject method) {
+    CfInstructionSubject cfInst = new CfInstructionSubject(instruction, method);
     if (cfInst.isInvoke()) {
-      return new InvokeCfInstructionSubject(this, instruction);
+      return new InvokeCfInstructionSubject(this, instruction, method);
     } else if (cfInst.isFieldAccess()) {
-      return new FieldAccessCfInstructionSubject(this, instruction);
+      return new FieldAccessCfInstructionSubject(this, instruction, method);
     } else if (cfInst.isNewInstance()) {
-      return new NewInstanceCfInstructionSubject(instruction);
+      return new NewInstanceCfInstructionSubject(instruction, method);
     } else if (cfInst.isConstString(JumboStringMode.ALLOW)) {
-      return new ConstStringCfInstructionSubject(instruction);
+      return new ConstStringCfInstructionSubject(instruction, method);
     } else {
       return cfInst;
     }
@@ -458,4 +464,8 @@
       // nothing to do
     }
   }
+
+  public RetraceBase retrace() {
+    return RetraceBaseImpl.create(mapping);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
index da545e4..c351e10 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
@@ -10,8 +10,8 @@
 
 public class ConstStringCfInstructionSubject extends CfInstructionSubject
     implements ConstStringInstructionSubject {
-  public ConstStringCfInstructionSubject(CfInstruction instruction) {
-    super(instruction);
+  public ConstStringCfInstructionSubject(CfInstruction instruction, MethodSubject method) {
+    super(instruction, method);
     assert isConstString(JumboStringMode.ALLOW);
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
index cb69144..ddff370 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
@@ -11,8 +11,8 @@
 
 public class ConstStringDexInstructionSubject extends DexInstructionSubject
     implements ConstStringInstructionSubject {
-  public ConstStringDexInstructionSubject(Instruction instruction) {
-    super(instruction);
+  public ConstStringDexInstructionSubject(Instruction instruction, MethodSubject method) {
+    super(instruction, method);
     assert isConstString(JumboStringMode.ALLOW);
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
index 8ae5609..0d98b29 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
@@ -12,10 +12,12 @@
 
   private final CodeInspector codeInspector;
   private final DexCode code;
+  private final MethodSubject methodSubject;
   private int index;
 
   DexInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
     this.codeInspector = codeInspector;
+    this.methodSubject = method;
     assert method.isPresent();
     Code code = method.getMethod().getCode();
     assert code != null && code.isDexCode();
@@ -33,6 +35,6 @@
     if (index == code.instructions.length) {
       throw new NoSuchElementException();
     }
-    return codeInspector.createInstructionSubject(code.instructions[index++]);
+    return codeInspector.createInstructionSubject(code.instructions[index++], methodSubject);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 53fb02c..c46c123 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -104,10 +104,13 @@
 import com.android.tools.r8.ir.code.WideConstant;
 
 public class DexInstructionSubject implements InstructionSubject {
-  protected final Instruction instruction;
 
-  public DexInstructionSubject(Instruction instruction) {
+  protected final Instruction instruction;
+  protected final MethodSubject method;
+
+  public DexInstructionSubject(Instruction instruction, MethodSubject method) {
     this.instruction = instruction;
+    this.method = method;
   }
 
   @Override
@@ -438,6 +441,11 @@
   }
 
   @Override
+  public MethodSubject getMethodSubject() {
+    return method;
+  }
+
+  @Override
   public boolean equals(Object other) {
     return other instanceof DexInstructionSubject
         && instruction.equals(((DexInstructionSubject) other).instruction);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java
index ae7d420..2e1d073 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java
@@ -11,8 +11,9 @@
     implements FieldAccessInstructionSubject {
   private final CodeInspector codeInspector;
 
-  public FieldAccessCfInstructionSubject(CodeInspector codeInspector, CfInstruction instruction) {
-    super(instruction);
+  public FieldAccessCfInstructionSubject(
+      CodeInspector codeInspector, CfInstruction instruction, MethodSubject method) {
+    super(instruction, method);
     this.codeInspector = codeInspector;
     assert isFieldAccess();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java
index 361213a..fcfdbbe 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java
@@ -11,8 +11,9 @@
 
   private final CodeInspector codeInspector;
 
-  public FieldAccessDexInstructionSubject(CodeInspector codeInspector, Instruction instruction) {
-    super(instruction);
+  public FieldAccessDexInstructionSubject(
+      CodeInspector codeInspector, Instruction instruction, MethodSubject method) {
+    super(instruction, method);
     this.codeInspector = codeInspector;
     assert isFieldAccess();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
index 0a53c91..a97f040 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
@@ -12,7 +12,7 @@
 
   private final DexAnnotation annotation;
 
-  public FoundAnnotationSubject(DexAnnotation annotation) {
+  FoundAnnotationSubject(DexAnnotation annotation) {
     this.annotation = annotation;
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 45276f4..adc0dbd 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import static com.android.tools.r8.KotlinTestBase.METADATA_TYPE;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -23,6 +26,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import java.util.function.Consumer;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public class FoundClassSubject extends ClassSubject {
 
@@ -324,7 +328,21 @@
     return dexClass.toSourceString();
   }
 
-  public TypeSubject asTypeSybject() {
+  public TypeSubject asTypeSubject() {
     return new TypeSubject(codeInspector, getDexClass().type);
   }
+
+  @Override
+  public KmClassSubject getKmClass() {
+    AnnotationSubject annotationSubject = annotation(METADATA_TYPE);
+    if (!annotationSubject.isPresent()) {
+      return new AbsentKmClassSubject();
+    }
+    KotlinClassMetadata metadata =
+        KotlinClassMetadataReader.toKotlinClassMetadata(
+            codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
+    assertTrue(metadata instanceof KotlinClassMetadata.Class);
+    KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) metadata;
+    return new FoundKmClassSubject(codeInspector, getDexClass(), kClass.toKmClass());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
new file mode 100644
index 0000000..358a016
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -0,0 +1,145 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeVisitor;
+
+public class FoundKmClassSubject extends KmClassSubject {
+  private final CodeInspector codeInspector;
+  private final DexClass clazz;
+  private final KmClass kmClass;
+
+  FoundKmClassSubject(CodeInspector codeInspector, DexClass clazz, KmClass kmClass) {
+    this.codeInspector = codeInspector;
+    this.clazz = clazz;
+    this.kmClass = kmClass;
+  }
+
+  @Override
+  public DexClass getDexClass() {
+    return clazz;
+  }
+
+  @Override
+  public KmClass getKmClass(Kotlin kotlin) {
+    return kmClass;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return !clazz.type.getInternalName().equals(kmClass.name);
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata
+    //   from scratch.
+    return false;
+  }
+
+  private String getDescriptorFromKmType(KmType kmType) {
+    if (kmType == null) {
+      return null;
+    }
+    Box<String> descriptor = new Box<>(null);
+    kmType.accept(new KmTypeVisitor() {
+      @Override
+      public void visitClass(String name) {
+        descriptor.set(DescriptorUtils.getDescriptorFromKotlinClassifier(name));
+      }
+    });
+    return descriptor.get();
+  }
+
+  @Override
+  public List<String> getSuperTypeDescriptors() {
+    return kmClass.getSupertypes().stream()
+        .map(this::getDescriptorFromKmType)
+        .filter(Objects::nonNull)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public List<String> getParameterTypeDescriptorsInFunctions() {
+    return kmClass.getFunctions().stream()
+        .flatMap(kmFunction ->
+            kmFunction.getValueParameters().stream()
+                .map(kmValueParameter -> getDescriptorFromKmType(kmValueParameter.getType()))
+                .filter(Objects::nonNull))
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public List<String> getReturnTypeDescriptorsInFunctions() {
+    return kmClass.getFunctions().stream()
+        .map(kmFunction -> getDescriptorFromKmType(kmFunction.getReturnType()))
+        .filter(Objects::nonNull)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public List<String> getReturnTypeDescriptorsInProperties() {
+    return kmClass.getProperties().stream()
+        .map(kmProperty -> getDescriptorFromKmType(kmProperty.getReturnType()))
+        .filter(Objects::nonNull)
+        .collect(Collectors.toList());
+  }
+
+  private ClassSubject getClassSubjectFromKmType(KmType kmType) {
+    String descriptor = getDescriptorFromKmType(kmType);
+    if (descriptor == null) {
+      return new AbsentClassSubject();
+    }
+    return codeInspector.clazz(Reference.classFromDescriptor(descriptor));
+  }
+
+  @Override
+  public List<ClassSubject> getSuperTypes() {
+    return kmClass.getSupertypes().stream()
+        .map(this::getClassSubjectFromKmType)
+        .filter(ClassSubject::isPresent)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public List<ClassSubject> getParameterTypesInFunctions() {
+    return kmClass.getFunctions().stream()
+        .flatMap(kmFunction ->
+            kmFunction.getValueParameters().stream()
+                .map(kmValueParameter -> getClassSubjectFromKmType(kmValueParameter.getType()))
+                .filter(ClassSubject::isPresent))
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public List<ClassSubject> getReturnTypesInFunctions() {
+    return kmClass.getFunctions().stream()
+        .map(kmFunction -> getClassSubjectFromKmType(kmFunction.getReturnType()))
+        .filter(ClassSubject::isPresent)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public List<ClassSubject> getReturnTypesInProperties() {
+    return kmClass.getProperties().stream()
+        .map(kmProperty -> getClassSubjectFromKmType(kmProperty.getReturnType()))
+        .filter(ClassSubject::isPresent)
+        .collect(Collectors.toList());
+  }
+}
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 0466c4e..071fb4f 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
@@ -29,15 +29,18 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.LocalVariableTable.LocalVariableTableEntry;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 public class FoundMethodSubject extends MethodSubject {
 
@@ -260,14 +263,14 @@
 
   private LineNumberTable getCfLineNumberTable(CfCode code) {
     int currentLine = -1;
-    Reference2IntMap<InstructionSubject> lineNumberTable =
-        new Reference2IntOpenHashMap<>(code.getInstructions().size());
+    Object2IntMap<InstructionSubject> lineNumberTable =
+        new Object2IntOpenHashMap<>(code.getInstructions().size());
     for (CfInstruction insn : code.getInstructions()) {
       if (insn instanceof CfPosition) {
         currentLine = ((CfPosition) insn).getPosition().line;
       }
       if (currentLine != -1) {
-        lineNumberTable.put(new CfInstructionSubject(insn), currentLine);
+        lineNumberTable.put(new CfInstructionSubject(insn, this), currentLine);
       }
     }
     return currentLine == -1 ? null : new LineNumberTable(lineNumberTable);
@@ -278,8 +281,7 @@
     if (debugInfo == null) {
       return null;
     }
-    Reference2IntMap<InstructionSubject> lineNumberTable =
-        new Reference2IntOpenHashMap<>(code.instructions.length);
+    Object2IntMap<InstructionSubject> lineNumberTable = new Object2IntOpenHashMap<>();
     DexDebugPositionState state =
         new DexDebugPositionState(debugInfo.startLine, getMethod().method);
     Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator();
@@ -288,7 +290,7 @@
       while (state.getCurrentPc() < offset && iterator.hasNext()) {
         iterator.next().accept(state);
       }
-      lineNumberTable.put(new DexInstructionSubject(insn), state.getCurrentLine());
+      lineNumberTable.put(new DexInstructionSubject(insn, this), state.getCurrentLine());
     }
     return new LineNumberTable(lineNumberTable);
   }
@@ -316,8 +318,8 @@
               localVariable.getLocal().signature == null
                   ? null
                   : localVariable.getLocal().signature.toString(),
-              new CfInstructionSubject(localVariable.getStart()),
-              new CfInstructionSubject(localVariable.getEnd())));
+              new CfInstructionSubject(localVariable.getStart(), this),
+              new CfInstructionSubject(localVariable.getEnd(), this)));
     }
     return new LocalVariableTable(builder.build());
   }
@@ -338,4 +340,20 @@
         ? new AbsentAnnotationSubject()
         : new FoundAnnotationSubject(annotation);
   }
+
+  @Override
+  public FoundMethodSubject asFoundMethodSubject() {
+    return this;
+  }
+
+  public MethodReference asMethodReference() {
+    DexMethod method = dexMethod.method;
+    return Reference.method(
+        Reference.classFromDescriptor(method.holder.toDescriptorString()),
+        method.name.toString(),
+        Arrays.stream(method.proto.parameters.values)
+            .map(type -> Reference.typeFromDescriptor(type.toDescriptorString()))
+            .collect(Collectors.toList()),
+        Reference.returnTypeFromDescriptor(method.proto.returnType.toDescriptorString()));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 25e10f4..9c67ece 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.retrace.RetraceBase;
+import com.android.tools.r8.retrace.RetraceMethodResult;
 
 public interface InstructionSubject {
 
@@ -107,4 +109,19 @@
   int size();
 
   InstructionOffsetSubject getOffset(MethodSubject methodSubject);
+
+  MethodSubject getMethodSubject();
+
+  default int getLineNumber() {
+    return getMethodSubject().getLineNumberTable().getLineForInstruction(this);
+  }
+
+  default RetraceMethodResult retracePosition(RetraceBase retraceBase) {
+    MethodSubject methodSubject = getMethodSubject();
+    assert methodSubject.isPresent();
+    int lineNumber = getLineNumber();
+    return retraceBase
+        .retrace(methodSubject.asFoundMethodSubject().asMethodReference())
+        .narrowByLine(lineNumber);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
index c5ceb74..e43ece1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
@@ -13,8 +13,9 @@
     implements InvokeInstructionSubject {
   private final CodeInspector codeInspector;
 
-  public InvokeCfInstructionSubject(CodeInspector codeInspector, CfInstruction instruction) {
-    super(instruction);
+  public InvokeCfInstructionSubject(
+      CodeInspector codeInspector, CfInstruction instruction, MethodSubject method) {
+    super(instruction, method);
     assert isInvoke();
     this.codeInspector = codeInspector;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
index 57a0e71..7f5c6ff 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
@@ -12,8 +12,9 @@
 
   private final CodeInspector codeInspector;
 
-  public InvokeDexInstructionSubject(CodeInspector codeInspector, Instruction instruction) {
-    super(instruction);
+  public InvokeDexInstructionSubject(
+      CodeInspector codeInspector, Instruction instruction, MethodSubject method) {
+    super(instruction, method);
     this.codeInspector = codeInspector;
     assert isInvoke();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
new file mode 100644
index 0000000..aac5196
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.kotlin.Kotlin;
+import java.util.List;
+import kotlinx.metadata.KmClass;
+
+public abstract class KmClassSubject extends Subject {
+  public abstract DexClass getDexClass();
+  public abstract KmClass getKmClass(Kotlin kotlin);
+
+  public List<String> getSuperTypeDescriptors() {
+    return null;
+  }
+
+  public List<String> getParameterTypeDescriptorsInFunctions() {
+    return null;
+  }
+
+  public List<String> getReturnTypeDescriptorsInFunctions() {
+    return null;
+  }
+
+  public List<String> getReturnTypeDescriptorsInProperties() {
+    return null;
+  }
+
+  public List<ClassSubject> getSuperTypes() {
+    return null;
+  }
+
+  public List<ClassSubject> getParameterTypesInFunctions() {
+    return null;
+  }
+
+  public List<ClassSubject> getReturnTypesInFunctions() {
+    return null;
+  }
+
+  public List<ClassSubject> getReturnTypesInProperties() {
+    return null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java
new file mode 100644
index 0000000..b3a0143
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.kotlin.Kotlin;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+// TODO(b/145824437): This is a dup of the same class in source to avoid the error while building
+//  keep rules for r8lib. Should be able to avoid this redundancy at build configuration level or
+//  change -printusage to apply mappings regarding relocated deps.
+class KotlinClassMetadataReader {
+  static KotlinClassMetadata toKotlinClassMetadata(
+      Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) {
+    Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
+    for (DexAnnotationElement element : metadataAnnotation.elements) {
+      elementMap.put(element.name, element);
+    }
+
+    DexAnnotationElement kind = elementMap.get(kotlin.metadata.kind);
+    if (kind == null) {
+      throw new MetadataError("element 'k' is missing.");
+    }
+    Integer k = (Integer) kind.value.getBoxedValue();
+    DexAnnotationElement metadataVersion = elementMap.get(kotlin.metadata.metadataVersion);
+    int[] mv = metadataVersion == null ? null : getUnboxedIntArray(metadataVersion.value, "mv");
+    DexAnnotationElement bytecodeVersion = elementMap.get(kotlin.metadata.bytecodeVersion);
+    int[] bv = bytecodeVersion == null ? null : getUnboxedIntArray(bytecodeVersion.value, "bv");
+    DexAnnotationElement data1 = elementMap.get(kotlin.metadata.data1);
+    String[] d1 = data1 == null ? null : getUnboxedStringArray(data1.value, "d1");
+    DexAnnotationElement data2 = elementMap.get(kotlin.metadata.data2);
+    String[] d2 = data2 == null ? null : getUnboxedStringArray(data2.value, "d2");
+    DexAnnotationElement extraString = elementMap.get(kotlin.metadata.extraString);
+    String xs = extraString == null ? null : getUnboxedString(extraString.value, "xs");
+    DexAnnotationElement packageName = elementMap.get(kotlin.metadata.packageName);
+    String pn = packageName == null ? null : getUnboxedString(packageName.value, "pn");
+    DexAnnotationElement extraInt = elementMap.get(kotlin.metadata.extraInt);
+    Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue();
+
+    KotlinClassHeader header = new KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi);
+    return KotlinClassMetadata.read(header);
+  }
+
+  private static int[] getUnboxedIntArray(DexValue v, String elementName) {
+    if (!(v instanceof DexValueArray)) {
+      throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+    }
+    DexValueArray intArrayValue = (DexValueArray) v;
+    DexValue[] values = intArrayValue.getValues();
+    int[] result = new int [values.length];
+    for (int i = 0; i < values.length; i++) {
+      result[i] = (Integer) values[i].getBoxedValue();
+    }
+    return result;
+  }
+
+  private static String[] getUnboxedStringArray(DexValue v, String elementName) {
+    if (!(v instanceof DexValueArray)) {
+      throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+    }
+    DexValueArray stringArrayValue = (DexValueArray) v;
+    DexValue[] values = stringArrayValue.getValues();
+    String[] result = new String [values.length];
+    for (int i = 0; i < values.length; i++) {
+      result[i] = getUnboxedString(values[i], elementName + "[" + i + "]");
+    }
+    return result;
+  }
+
+  private static String getUnboxedString(DexValue v, String elementName) {
+    if (!(v instanceof DexValueString)) {
+      throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+    }
+    return ((DexValueString) v).getValue().toString();
+  }
+
+  private static class MetadataError extends RuntimeException {
+    MetadataError(String cause) {
+      super(cause);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
index 7c9c30f..a90707f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
@@ -4,16 +4,20 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import it.unimi.dsi.fastutil.ints.IntCollection;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
 
 public class LineNumberTable {
-  private final Reference2IntMap<InstructionSubject> lineNumberTable;
+  private final Object2IntMap<InstructionSubject> lineNumberTable;
 
-  public LineNumberTable(Reference2IntMap<InstructionSubject> lineNumberTable) {
+  public LineNumberTable(Object2IntMap<InstructionSubject> lineNumberTable) {
     this.lineNumberTable = lineNumberTable;
   }
 
   public IntCollection getLines() {
     return lineNumberTable.values();
   }
+
+  public int getLineForInstruction(InstructionSubject subject) {
+    return lineNumberTable.getInt(subject);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 417e40e..d9f11d6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -6,7 +6,16 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceMethodResult.Element;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -48,6 +57,10 @@
       type = "method";
     } else if (subject instanceof FieldSubject) {
       type = "field";
+    } else if (subject instanceof AnnotationSubject) {
+      type = "annotation";
+    } else if (subject instanceof KmClassSubject) {
+      type = "@Metadata.KmClass";
     }
     return type;
   }
@@ -60,6 +73,10 @@
       name = ((MethodSubject) subject).getOriginalName();
     } else if (subject instanceof FieldSubject) {
       name = ((FieldSubject) subject).getOriginalName();
+    } else if (subject instanceof AnnotationSubject) {
+      name = ((AnnotationSubject) subject).getAnnotation().type.toSourceString();
+    } else if (subject instanceof KmClassSubject) {
+      name = ((KmClassSubject) subject).getDexClass().toSourceString();
     }
     return name;
   }
@@ -339,4 +356,189 @@
       }
     };
   }
+
+  public static Matcher<RetraceMethodResult> isInlineFrame() {
+    return new TypeSafeMatcher<RetraceMethodResult>() {
+      @Override
+      protected boolean matchesSafely(RetraceMethodResult item) {
+        return !item.isAmbiguous() && item.stream().count() > 1;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is not an inline frame");
+      }
+    };
+  }
+
+  public static Matcher<RetraceMethodResult> isInlineStack(InlinePosition startPosition) {
+    return new TypeSafeMatcher<RetraceMethodResult>() {
+      @Override
+      protected boolean matchesSafely(RetraceMethodResult item) {
+        Box<InlinePosition> currentPosition = new Box<>(startPosition);
+        Box<Boolean> returnValue = new Box<>();
+        item.forEach(
+            element -> {
+              boolean sameMethod;
+              InlinePosition currentInline = currentPosition.get();
+              if (currentInline == null) {
+                returnValue.set(false);
+                return;
+              }
+              if (currentInline.hasMethodSubject()) {
+                sameMethod =
+                    element
+                        .getMethodReference()
+                        .equals(
+                            currentInline.methodSubject.asFoundMethodSubject().asMethodReference());
+              } else {
+                MethodReference methodReference = element.getMethodReference();
+                sameMethod =
+                    methodReference.getMethodName().equals(currentInline.methodName)
+                        || methodReference
+                            .getHolderClass()
+                            .getTypeName()
+                            .equals(currentInline.holder);
+              }
+              boolean samePosition =
+                  element.getOriginalLineNumber(currentInline.minifiedPosition)
+                      == currentInline.originalPosition;
+              if (!returnValue.isSet() || returnValue.get()) {
+                returnValue.set(sameMethod & samePosition);
+              }
+              currentPosition.set(currentInline.caller);
+            });
+        return returnValue.isSet() && returnValue.get();
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is not matching the inlining stack");
+      }
+    };
+  }
+
+  public static Matcher<RetraceMethodResult> isInlinedInto(InlinePosition inlinePosition) {
+    return new TypeSafeMatcher<RetraceMethodResult>() {
+      @Override
+      protected boolean matchesSafely(RetraceMethodResult item) {
+        if (item.isAmbiguous() || !inlinePosition.methodSubject.isPresent()) {
+          return false;
+        }
+        List<Element> references = item.stream().collect(Collectors.toList());
+        if (references.size() < 2) {
+          return false;
+        }
+        Element lastElement = ListUtils.last(references);
+        if (!lastElement
+            .getMethodReference()
+            .equals(inlinePosition.methodSubject.asFoundMethodSubject().asMethodReference())) {
+          return false;
+        }
+        return lastElement.getFirstLineNumberOfOriginalRange() == inlinePosition.originalPosition;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is not inlined into " + inlinePosition.getMethodName());
+      }
+    };
+  }
+
+  public static Matcher<StackTrace> containsInlinePosition(InlinePosition inlinePosition) {
+    return new TypeSafeMatcher<StackTrace>() {
+      @Override
+      protected boolean matchesSafely(StackTrace item) {
+        return containsInlineStack(item, 0, inlinePosition);
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("cannot be found in stack trace");
+      }
+
+      private boolean containsInlineStack(
+          StackTrace stackTrace, int index, InlinePosition currentPosition) {
+        if (currentPosition == null) {
+          return true;
+        }
+        if (index >= stackTrace.size()) {
+          return false;
+        }
+        StackTraceLine stackTraceLine = stackTrace.get(index);
+        boolean resultHere =
+            stackTraceLine.className.equals(currentPosition.getClassName())
+                && stackTraceLine.methodName.equals(currentPosition.getMethodName())
+                && stackTraceLine.lineNumber == currentPosition.originalPosition;
+        if (resultHere && containsInlineStack(stackTrace, index + 1, currentPosition.caller)) {
+          return true;
+        }
+        // Maybe the inline position starts from the top on the next position.
+        return containsInlineStack(stackTrace, index + 1, inlinePosition);
+      }
+    };
+  }
+
+  public static class InlinePosition {
+    private final FoundMethodSubject methodSubject;
+    private final String holder;
+    private final String methodName;
+    private final int minifiedPosition;
+    private final int originalPosition;
+
+    private InlinePosition caller;
+
+    private InlinePosition(
+        FoundMethodSubject methodSubject,
+        String holder,
+        String methodName,
+        int minifiedPosition,
+        int originalPosition) {
+      this.methodSubject = methodSubject;
+      this.holder = holder;
+      this.methodName = methodName;
+      this.minifiedPosition = minifiedPosition;
+      this.originalPosition = originalPosition;
+      assert methodSubject != null || holder != null;
+      assert methodSubject != null || methodName != null;
+    }
+
+    public static InlinePosition create(
+        FoundMethodSubject methodSubject, int minifiedPosition, int originalPosition) {
+      return new InlinePosition(methodSubject, null, null, minifiedPosition, originalPosition);
+    }
+
+    public static InlinePosition create(
+        String holder, String methodName, int minifiedPosition, int originalPosition) {
+      return new InlinePosition(null, holder, methodName, minifiedPosition, originalPosition);
+    }
+
+    public static InlinePosition stack(InlinePosition... stack) {
+      setCaller(1, stack);
+      return stack[0];
+    }
+
+    private static void setCaller(int index, InlinePosition... stack) {
+      assert index > 0;
+      if (index >= stack.length) {
+        return;
+      }
+      stack[index - 1].caller = stack[index];
+      setCaller(index + 1, stack);
+    }
+
+    boolean hasMethodSubject() {
+      return methodSubject != null;
+    }
+
+    String getMethodName() {
+      return hasMethodSubject() ? methodSubject.getOriginalName(false) : methodName;
+    }
+
+    String getClassName() {
+      return hasMethodSubject()
+          ? methodSubject.asMethodReference().getHolderClass().getTypeName()
+          : holder;
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 4fe5fb0..af6e1a4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -41,6 +41,10 @@
 
   public abstract boolean isVirtual();
 
+  public FoundMethodSubject asFoundMethodSubject() {
+    return null;
+  }
+
   @Override
   public abstract MethodSignature getOriginalSignature();
 
@@ -82,6 +86,11 @@
     return Streams.stream(iterateInstructions());
   }
 
+  public void getLineNumberForInstruction(InstructionSubject subject) {
+    assert hasLineNumberTable();
+    getLineNumberTable().getLineForInstruction(subject);
+  }
+
   @Override
   public MethodSubject asMethodSubject() {
     return this;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
index d3b0a93..f4e0a95 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
@@ -10,8 +10,8 @@
 
 public class NewInstanceCfInstructionSubject extends CfInstructionSubject
     implements NewInstanceInstructionSubject {
-  public NewInstanceCfInstructionSubject(CfInstruction instruction) {
-    super(instruction);
+  public NewInstanceCfInstructionSubject(CfInstruction instruction, MethodSubject method) {
+    super(instruction, method);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
index a396468..ef883fa 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
@@ -10,8 +10,8 @@
 
 public class NewInstanceDexInstructionSubject extends DexInstructionSubject
     implements NewInstanceInstructionSubject {
-  public NewInstanceDexInstructionSubject(Instruction instruction) {
-    super(instruction);
+  public NewInstanceDexInstructionSubject(Instruction instruction, MethodSubject method) {
+    super(instruction, method);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 59376e6..fe98a17 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.experimental.graphinfo.GraphNode;
 import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
 import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
@@ -38,6 +37,7 @@
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.BiPredicate;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 import org.junit.Assert;
diff --git a/src/test/r8OnArtBackport/MockedPath.java b/src/test/r8OnArtBackport/MockedPath.java
index c5147df..3caa9cd 100644
--- a/src/test/r8OnArtBackport/MockedPath.java
+++ b/src/test/r8OnArtBackport/MockedPath.java
@@ -99,6 +99,11 @@
   }
 
   @Override
+  public Path resolve(String other) {
+    return new MockedPath(new File(wrappedFile.getPath(), other));
+  }
+
+  @Override
   public Path relativize(Path other) {
     throw new RuntimeException("Mocked Path does not implement relativize");
   }
diff --git a/third_party/openjdk/desugar_jdk_libs.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs.tar.gz.sha1
index fad80cc..7b8d748 100644
--- a/third_party/openjdk/desugar_jdk_libs.tar.gz.sha1
+++ b/third_party/openjdk/desugar_jdk_libs.tar.gz.sha1
@@ -1 +1 @@
-f06937c3611cbc82d08ff043aa5c176ce6989295
\ No newline at end of file
+2fdc16c4253420d7e240d5cf109bda5856984a8d
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 903f3b8..f866564 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -19,7 +19,6 @@
 # repository to fetch the artifact com.android.tools:desugar_jdk_libs:1.0.0
 
 import archive
-import defines
 import git_utils
 import optparse
 import os
@@ -28,19 +27,6 @@
 import subprocess
 import sys
 import utils
-import zipfile
-
-if defines.IsLinux():
-  JDK8_JAVAC = os.path.join(
-      defines.THIRD_PARTY, 'openjdk', 'jdk8', 'linux-x86', 'bin', 'javac')
-elif defines.IsOsX():
-  JDK8_JAVAC = os.path.join(
-      defines.THIRD_PARTY, 'openjdk', 'jdk8', 'darwin-x86', 'bin', 'javac')
-elif defines.IsWindows():
-  raise Exception('Cannot compile using JDK8 on Windows hence cannot archive.')
-
-CONVERSION_FOLDER = os.path.join(
-    defines.REPO_ROOT, 'src', 'test', 'desugaredLibraryConversions')
 
 VERSION_FILE = 'VERSION.txt'
 LIBRARY_NAME = 'desugar_jdk_libs'
@@ -64,15 +50,12 @@
 
 def GetVersion():
   with open(VERSION_FILE, 'r') as version_file:
-    lines = version_file.readlines()
+    lines = [line.strip() for line in version_file.readlines()]
+    lines = [line for line in lines if not line.startswith('#')]
     if len(lines) != 1:
       raise Exception('Version file '
           + VERSION_FILE + ' is expected to have exactly one line')
     version = lines[0].strip()
-    if (version == '1.0.1'):
-      raise Exception('Version file ' + VERSION_FILE + 'cannot have version 1.0.1')
-    if (version == '1.0.0'):
-      version = '1.0.1'
     utils.check_basic_semver_version(
         version, 'in version file ' + VERSION_FILE)
     return version
@@ -94,13 +77,6 @@
     print('File available at: %s' %
         destination.replace('gs://', 'http://storage.googleapis.com/', 1))
 
-def GetFilesInFolder(folder):
-  resulting_files = []
-  for root, dirs, files in os.walk(folder):
-    for name in files:
-      resulting_files.append(os.path.join(root, name))
-  assert len(resulting_files) > 0
-  return resulting_files
 
 def Main(argv):
   (options, args) = ParseOptions(argv)
@@ -146,46 +122,12 @@
       utils.PrintCmd(cmd)
       subprocess.check_call(cmd)
 
-      # Compile the stubs for conversion files compilation.
-      stub_compiled_folder = os.path.join(checkout_dir, 'stubs')
-      os.mkdir(stub_compiled_folder)
-      all_stubs = GetFilesInFolder(os.path.join(CONVERSION_FOLDER, 'stubs'))
-      cmd = [JDK8_JAVAC, '-d', stub_compiled_folder] + all_stubs
-      utils.PrintCmd(cmd)
-      subprocess.check_call(cmd)
-
-      # Compile the conversion files.
-      conversions_compiled_folder = os.path.join(checkout_dir, 'conversions')
-      os.mkdir(conversions_compiled_folder)
-      all_conversions = GetFilesInFolder(
-          os.path.join(CONVERSION_FOLDER, 'conversions'))
-      cmd = [JDK8_JAVAC, '-cp', stub_compiled_folder, '-d',
-             conversions_compiled_folder] + all_conversions
-      utils.PrintCmd(cmd)
-      subprocess.check_call(cmd)
-
       # Locate the library jar and the maven zip with the jar from the
       # bazel build.
       library_jar = os.path.join(
           'bazel-bin', 'src', 'share', 'classes', 'java', 'libjava.jar')
       maven_zip = os.path.join('bazel-bin', LIBRARY_NAME +'.zip')
 
-      # Make a writable copy of the jar.
-      jar_folder = os.path.join(checkout_dir, 'jar')
-      os.mkdir(jar_folder)
-      shutil.copy(library_jar, jar_folder)
-      library_jar = os.path.join(jar_folder,'libjava.jar')
-      os.chmod(library_jar, 0o777)
-
-      # Add conversion classes into the jar.
-      all_compiled_conversions = GetFilesInFolder(conversions_compiled_folder)
-      with zipfile.ZipFile(library_jar, mode='a', allowZip64=True) as jar:
-        for clazz in all_compiled_conversions:
-          jar.write(clazz,arcname=os.path.relpath(
-              clazz,
-              conversions_compiled_folder),
-              compress_type = zipfile.ZIP_DEFLATED)
-
       storage_path = LIBRARY_NAME + '/' + version
       # Upload the jar file with the library.
       destination = archive.GetUploadDestination(
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 39f60c0..366b908 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -11,6 +11,9 @@
 import subprocess
 import sys
 import urllib
+import xml
+import xml.etree.ElementTree as et
+import zipfile
 
 import update_prebuilds_in_android
 import utils
@@ -19,6 +22,11 @@
 R8_VERSION_FILE = os.path.join(
     'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
 THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
+ADMRT = '/google/data/ro/teams/android-devtools-infra/tools/admrt'
+
+DESUGAR_JDK_LIBS = 'desugar_jdk_libs'
+DESUGAR_JDK_LIBS_CONFIGURATION = DESUGAR_JDK_LIBS + '_configuration'
+ANDROID_TOOLS_PACKAGE = 'com.android.tools'
 
 
 def prepare_release(args):
@@ -262,8 +270,7 @@
 def g4_change(version, r8version):
   return subprocess.check_output(
       'g4 change --desc "Update R8 to version %s %s\n\n'
-      'IGNORE_COMPLIANCELINT=D8 and R8 are built externally to produce a fully '
-      'tested R8lib"' % (version, r8version),
+      'IGNORE_COMPLIANCELINT=b/145307639"' % (version, r8version),
       shell=True)
 
 
@@ -289,7 +296,7 @@
 def prepare_google3(args):
   assert args.version
   # Check if an existing client exists.
-  if not options.use_existing_work_branch:
+  if not args.use_existing_work_branch:
     if ':update-r8:' in subprocess.check_output('g4 myclients', shell=True):
       print "Remove the existing 'update-r8' client before continuing."
       sys.exit(1)
@@ -372,6 +379,139 @@
   return release_google3
 
 
+def prepare_desugar_library(args):
+
+  def make_release(args):
+    library_version = args.desugar_library[0]
+    configuration_hash = args.desugar_library[1]
+
+    library_archive = DESUGAR_JDK_LIBS + '.zip'
+    library_artifact_id = \
+        '%s:%s:%s' % (ANDROID_TOOLS_PACKAGE, DESUGAR_JDK_LIBS, library_version)
+
+    with utils.TempDir() as temp:
+      with utils.ChangedWorkingDirectory(temp):
+        download_file(
+          '%s/%s' % (DESUGAR_JDK_LIBS, library_version),
+          library_archive,
+          library_archive)
+        configuration_archive = DESUGAR_JDK_LIBS_CONFIGURATION + '.zip'
+        configuration_artifact_id = \
+            download_configuration(configuration_hash, configuration_archive)
+
+        print 'Preparing maven release of:'
+        print '  %s' % library_artifact_id
+        print '  %s' % configuration_artifact_id
+        print
+
+        admrt_stage(
+          [library_archive, configuration_archive],
+          [library_artifact_id, configuration_artifact_id],
+          args)
+
+        admrt_lorry(
+          [library_archive, configuration_archive],
+          [library_artifact_id, configuration_artifact_id],
+          args)
+
+  return make_release
+
+
+def download_configuration(hash, archive):
+  print
+  print 'Downloading %s from GCS' % archive
+  print
+  download_file('master/' + hash, archive, archive)
+  zip = zipfile.ZipFile(archive)
+  zip.extractall()
+  dirs = os.listdir(
+    os.path.join('com', 'android', 'tools', DESUGAR_JDK_LIBS_CONFIGURATION))
+  if len(dirs) != 1:
+    print 'Unexpected archive content, %s' + dirs
+    sys.exit(1)
+
+  version = dirs[0]
+  pom_file = os.path.join(
+    'com',
+    'android',
+    'tools',
+    DESUGAR_JDK_LIBS_CONFIGURATION,
+    version,
+    '%s-%s.pom' % (DESUGAR_JDK_LIBS_CONFIGURATION, version))
+  version_from_pom = extract_version_from_pom(pom_file)
+  if version != version_from_pom:
+    print 'Version mismatch, %s != %s' % (version, version_from_pom)
+    sys.exit(1)
+  return '%s:%s:%s' % \
+      (ANDROID_TOOLS_PACKAGE, DESUGAR_JDK_LIBS_CONFIGURATION, version)
+
+def extract_version_from_pom(pom_file):
+    ns = "http://maven.apache.org/POM/4.0.0"
+    xml.etree.ElementTree.register_namespace('', ns)
+    tree = xml.etree.ElementTree.ElementTree()
+    tree.parse(pom_file)
+    return tree.getroot().find("{%s}version" % ns).text
+
+
+def admrt_stage(archives, artifact_ids, args):
+  if args.dry_run:
+    print 'Dry-run, just copying archives to %s' % args.dry_run_output
+    for archive in archives:
+      print 'Copying: %s' % archive
+      shutil.copyfile(archive, os.path.join(args.dry_run_output, archive))
+    return
+
+  admrt(archives, 'stage')
+
+  jdk9_home = os.path.join(
+      utils.REPO_ROOT, 'third_party', 'openjdk', 'openjdk-9.0.4', 'linux')
+  print
+  print "Use the following commands to test with 'redir':"
+  print
+  print 'export BUCKET_PATH=/studio_staging/maven2/<user>/<id>'
+  print '/google/data/ro/teams/android-devtools-infra/tools/redir \\'
+  print '  --alsologtostderr \\'
+  print '  --gcs_bucket_path=$BUCKET_PATH \\'
+  print '  --port=1480'
+  print
+  print "When the 'redir' server is running use the following commands"
+  print 'to retreive the artifact:'
+  print
+  print 'rm -rf /tmp/maven_repo_local'
+  print ('JAVA_HOME=%s ' % jdk9_home
+      + 'mvn org.apache.maven.plugins:maven-dependency-plugin:2.4:get \\')
+  print '  -Dmaven.repo.local=/tmp/maven_repo_local \\'
+  print '  -DremoteRepositories=http://localhost:1480 \\'
+  print '  -Dartifact=%s \\' % artifact_ids[0]
+  print '  -Ddest=%s' % archives[0]
+  print
+
+
+def admrt_lorry(archives, artifact_ids, args):
+  if args.dry_run:
+    print 'Dry run - no lorry action'
+    return
+
+  print
+  print 'Continue with running in lorry mode for release of:'
+  for artifact_id in artifact_ids:
+    print '  %s' % artifact_id
+  input = raw_input('[y/N]:')
+
+  if input != 'y':
+    print 'Aborting release to Google maven'
+    sys.exit(1)
+
+  admrt(archives, 'lorry')
+
+
+def admrt(archives, action):
+  cmd = [ADMRT, '--archives']
+  cmd.extend(archives)
+  cmd.extend(['--action', action])
+  subprocess.check_call()
+
+
 def branch_change_diff(diff, old_version, new_version):
   invalid_line = None
   for line in diff.splitlines():
@@ -496,19 +636,23 @@
   group.add_argument('--dev-release',
                       metavar=('<master hash>'),
                       help='The hash to use for the new dev version of R8')
+  group.add_argument('--version',
+                      metavar=('<version>'),
+                      help='The new version of R8 (e.g., 1.4.51) to release to selected channels')
+  group.add_argument('--desugar-library',
+                      nargs=2,
+                      metavar=('<version>', '<configuration hash>'),
+                      help='The new version of com.android.tools:desugar_jdk_libs')
+  group.add_argument('--new-dev-branch',
+                      nargs=2,
+                      metavar=('<version>', '<master hash>'),
+                      help='Create a new branch starting a version line (e.g. 2.0)')
   result.add_argument('--dev-pre-cherry-pick',
                       metavar=('<master hash(s)>'),
                       default=[],
                       action='append',
                       help='List of commits to cherry pick before doing full '
                            'merge, mostly used for reverting cherry picks')
-  group.add_argument('--version',
-                      metavar=('<version>'),
-                      help='The new version of R8 (e.g., 1.4.51) to release to selected channels')
-  group.add_argument('--new-dev-branch',
-                      nargs=2,
-                      metavar=('<version>', '<master hash>'),
-                      help='Create a new branch starting a version line (e.g. 2.0)')
   result.add_argument('--no-sync', '--no_sync',
                       default=False,
                       action='store_true',
@@ -542,6 +686,10 @@
                       default=False,
                       action='store_true',
                       help='Only perform non-commiting tasks and print others.')
+  result.add_argument('--dry-run-output', '--dry_run_output',
+                      default=os.getcwd(),
+                      metavar=('<path>'),
+                      help='Location for dry run output.')
   args = result.parse_args()
   if args.version and not 'dev' in args.version and args.bug == []:
     print "When releasing a release version add the list of bugs by using '--bug'"
@@ -570,7 +718,9 @@
       sys.exit(1)
     targets_to_run.append(prepare_release(args))
 
-  if args.google3 or (args.studio and not args.no_sync):
+  if (args.google3
+      or (args.studio and not args.no_sync)
+      or (args.desugar_library and not args.dry_run)):
     utils.check_prodacces()
 
   if args.google3:
@@ -580,6 +730,9 @@
   if args.aosp:
     targets_to_run.append(prepare_aosp(args))
 
+  if args.desugar_library:
+    targets_to_run.append(prepare_desugar_library(args))
+
   final_results = []
   for target_closure in targets_to_run:
     final_results.append(target_closure(args))