Merge commit '2d3c611f14193325be90abc1921bd68c075efc4e' into dev-release
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 2b6eb5f..ee1883c 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -149,6 +149,33 @@
       }
     }
     builders {
+      name: "linux-jdk8"
+      mixins: "linux"
+      mixins: "normal"
+      priority: 26
+      recipe {
+        properties_j: "test_options:[\"--runtimes=jdk8\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
+      }
+    }
+    builders {
+      name: "linux-jdk9"
+      mixins: "linux"
+      mixins: "normal"
+      priority: 26
+      recipe {
+        properties_j: "test_options:[\"--runtimes=jdk9\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
+      }
+    }
+    builders {
+      name: "linux-jdk8_9"
+      mixins: "linux"
+      mixins: "normal"
+      priority: 26
+      recipe {
+        properties_j: "test_options:[\"--runtimes=jdk8:jdk9\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
+      }
+    }
+    builders {
       name: "linux_horizontal"
       mixins: "linux"
       mixins: "normal"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 90e3efc..bec8477 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -16,6 +16,21 @@
     short_name: "linux"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk8"
+    category: "R8"
+    short_name: "jdk8"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk9"
+    category: "R8"
+    short_name: "jdk9"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk8_9"
+    category: "R8"
+    short_name: "jdk8_9"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux_horizontal"
     category: "R8"
     short_name: "horizontal"
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg
index e417700..4ed7592 100644
--- a/infra/config/global/luci-notify.cfg
+++ b/infra/config/global/luci-notify.cfg
@@ -29,6 +29,21 @@
     repository: "https://r8.googlesource.com/r8"
   }
   builders {
+    name: "linux-jdk8"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
+    name: "linux-jdk9"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
+    name: "linux-jdk8_9"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
     name: "linux_horizontal"
     bucket: "ci"
     repository: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index fcf4105..634885f 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -27,6 +27,9 @@
   }
   triggers: "archive"
   triggers: "linux"
+  triggers: "linux-jdk8"
+  triggers: "linux-jdk9"
+  triggers: "linux-jdk8_9"
   triggers: "linux_horizontal"
   triggers: "linux-android-4.0.4"
   triggers: "linux-android-4.4.4"
@@ -138,6 +141,49 @@
 }
 
 job {
+  id: "linux-jdk8"
+  acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 2
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-jdk8"
+  }
+}
+
+job {
+  id: "linux-jdk9"
+  acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 2
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-jdk9"
+  }
+}
+
+job {
+  id: "linux-jdk8_9"
+  acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 2
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-jdk8_9"
+  }
+}
+
+
+job {
   id: "linux_horizontal"
   acl_sets: "default"
   triggering_policy: {
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 18b86c7..45878c9 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -19,6 +19,7 @@
 
   protected static final String MIN_API_FLAG = "--min-api";
   protected static final String THREAD_COUNT_FLAG = "--thread-count";
+  protected static final String MAP_DIAGNOSTICS = "--map-diagnostics";
 
   static final Iterable<String> ASSERTIONS_USAGE_MESSAGE =
       Arrays.asList(
@@ -43,6 +44,17 @@
           "                          # the number will be based on heuristics taking the number",
           "                          # of cores into account.");
 
+  static final Iterable<String> MAP_DIAGNOSTICS_USAGE_MESSAGE =
+      Arrays.asList(
+          "  " + MAP_DIAGNOSTICS + "[:<type>] <from-level> <to-level>",
+          "                          # Map diagnostics of <type> (default any) reported as",
+          "                          # <from-level> to <to-level> where <from-level> and",
+          "                          # <to-level> are one of 'info', 'warning', or 'error' and the",
+          "                          # optional <type> is either the simple or fully qualified",
+          "                          # Java type name of a diagnostic. If <type> is unspecified,",
+          "                          # all diagnostics at <from-level> will be mapped.",
+          "                          # Note that fatal compiler errors cannot be mapped.");
+
   public static void parsePositiveIntArgument(
       Consumer<Diagnostic> errorConsumer,
       String flag,
@@ -140,6 +152,52 @@
     }
   }
 
+  private DiagnosticsLevel tryParseLevel(B builder, String arg, Origin origin) {
+    if (arg.equals("error")) {
+      return DiagnosticsLevel.ERROR;
+    }
+    if (arg.equals("warning")) {
+      return DiagnosticsLevel.WARNING;
+    }
+    if (arg.equals("info")) {
+      return DiagnosticsLevel.INFO;
+    }
+    builder.error(
+        new StringDiagnostic(
+            "Invalid diagnostics level '"
+                + arg
+                + "'. Valid levels are 'error', 'warning' and 'info'.",
+            origin));
+
+    return null;
+  }
+
+  int tryParseMapDiagnostics(B builder, String arg, String[] args, int argsIndex, Origin origin) {
+    if (!arg.startsWith(MAP_DIAGNOSTICS)) {
+      return -1;
+    }
+    if (args.length <= argsIndex + 2) {
+      builder.error(new StringDiagnostic("Missing argument(s) for " + arg + ".", origin));
+      return args.length - argsIndex;
+    }
+    String remaining = arg.substring(MAP_DIAGNOSTICS.length());
+    String diagnosticsClassName = "";
+    if (remaining.length() > 0) {
+      if (remaining.length() == 1 || remaining.charAt(0) != ':') {
+        builder.error(
+            new StringDiagnostic("Invalid diagnostics type specification " + arg + ".", origin));
+        return 0;
+      }
+      diagnosticsClassName = remaining.substring(1);
+    }
+    DiagnosticsLevel from = tryParseLevel(builder, args[argsIndex + 1], origin);
+    DiagnosticsLevel to = tryParseLevel(builder, args[argsIndex + 2], origin);
+    if (from != null && to != null) {
+      builder.getReporter().addDiagnosticsLevelMapping(from, diagnosticsClassName, to);
+    }
+    return 2;
+  }
+
   /**
    * This method must match the lookup in
    * {@link com.android.tools.r8.JdkClassFileProvider#fromJdkHome}.
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 0a38fb5..8e56a8e 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -249,7 +249,10 @@
       Marker marker = options.getMarker(Tool.D8);
       Set<Marker> markers = new HashSet<>(appView.dexItemFactory().extractMarkers());
       // TODO(b/166617364): Don't add an additional marker when desugaring is turned off.
-      if (hasClassResources && (options.desugarState != DesugarState.OFF || markers.isEmpty())) {
+      if (hasClassResources
+          && (options.desugarState != DesugarState.OFF
+              || markers.isEmpty()
+              || (markers.size() == 1 && markers.iterator().next().isL8()))) {
         markers.add(marker);
       }
       Marker.checkCompatibleDesugaredLibrary(markers, options.reporter);
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 2ed9890..d705c8e 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -144,6 +144,7 @@
                   "                          # Output resulting main dex list in <file>."),
               ASSERTIONS_USAGE_MESSAGE,
               THREAD_COUNT_USAGE_MESSAGE,
+              MAP_DIAGNOSTICS_USAGE_MESSAGE,
               Arrays.asList(
                   "  --version               # Print the version of d8.",
                   "  --help                  # Print this message.")));
@@ -276,10 +277,15 @@
       } else if (arg.equals("--desugared-lib")) {
         builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
       } else if (arg.startsWith("--")) {
-        if (!tryParseAssertionArgument(builder, arg, origin)) {
-          builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
+        if (tryParseAssertionArgument(builder, arg, origin)) {
           continue;
         }
+        int argsConsumed = tryParseMapDiagnostics(builder, arg, expandedArgs, i, origin);
+        if (argsConsumed >= 0) {
+          i += argsConsumed;
+          continue;
+        }
+        builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
       } else if (arg.startsWith("@")) {
         builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin));
       } else {
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index f3acbec..b77db45 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -356,7 +356,7 @@
 
   private void run() throws Exception {
     // Run over all the API levels that the desugared library can be compiled with.
-    for (int apiLevel = AndroidApiLevel.Q.getLevel();
+    for (int apiLevel = AndroidApiLevel.LATEST.getLevel();
         apiLevel >= desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel();
         apiLevel--) {
       System.out.println("Generating lint files for compile API " + apiLevel);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 221df4c..1141688 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -316,6 +316,7 @@
         r8Builder.addProguardConfiguration(
             libraryConfiguration.getExtraKeepRules(), Origin.unknown());
         r8Builder.addProguardConfigurationFiles(proguardConfigFiles);
+        r8Builder.setDisableDesugaring(true);
         r8Command = r8Builder.makeCommand();
       } else if (!(getProgramConsumer() instanceof ClassFileConsumer)) {
         l8CfConsumer = new InMemoryJarContent();
@@ -333,6 +334,7 @@
             inputs.getLibraryResourceProviders()) {
           d8Builder.addLibraryResourceProvider(libraryResourceProvider);
         }
+        d8Builder.setDisableDesugaring(true);
         d8Command = d8Builder.makeCommand();
       } else {
         assert getProgramConsumer() instanceof ClassFileConsumer;
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index e816320..0a57389 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -55,6 +55,7 @@
                       + " (json)."),
               ASSERTIONS_USAGE_MESSAGE,
               THREAD_COUNT_USAGE_MESSAGE,
+              MAP_DIAGNOSTICS_USAGE_MESSAGE,
               Arrays.asList(
                   "  --version               # Print the version of l8.",
                   "  --help                  # Print this message.")));
@@ -156,10 +157,15 @@
         parsePositiveIntArgument(
             builder::error, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
       } else if (arg.startsWith("--")) {
-        if (!tryParseAssertionArgument(builder, arg, origin)) {
-          builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
+        if (tryParseAssertionArgument(builder, arg, origin)) {
           continue;
         }
+        int argsConsumed = tryParseMapDiagnostics(builder, arg, expandedArgs, i, origin);
+        if (argsConsumed >= 0) {
+          i += argsConsumed;
+          continue;
+        }
+        builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
       } else {
         builder.addProgramFiles(Paths.get(arg));
       }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5e8e3a6..dad6c63 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -313,6 +313,7 @@
       }
       if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
         DesugaredLibraryRetargeter.checkForAssumedLibraryTypes(appView);
+        DesugaredLibraryRetargeter.amendLibraryWithRetargetedMembers(appView);
       }
       InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
       BackportedMethodRewriter.registerAssumedLibraryTypes(options);
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index b796705..3cb01e1 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -100,6 +100,7 @@
                   "                          # Output the full main-dex list in <file>."),
               ASSERTIONS_USAGE_MESSAGE,
               THREAD_COUNT_USAGE_MESSAGE,
+              MAP_DIAGNOSTICS_USAGE_MESSAGE,
               Arrays.asList(
                   "  --version               # Print the version of r8.",
                   "  --help                  # Print this message.")));
@@ -262,10 +263,15 @@
       } else if (arg.equals("--no-data-resources")) {
         state.includeDataResources = false;
       } else if (arg.startsWith("--")) {
-        if (!tryParseAssertionArgument(builder, arg, argsOrigin)) {
-          builder.error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
+        if (tryParseAssertionArgument(builder, arg, argsOrigin)) {
           continue;
         }
+        int argsConsumed = tryParseMapDiagnostics(builder, arg, expandedArgs, i, argsOrigin);
+        if (argsConsumed >= 0) {
+          i += argsConsumed;
+          continue;
+        }
+        builder.error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
       } else if (arg.startsWith("@")) {
         builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", argsOrigin));
       } else {
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index e3584ec..b43a42c 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -115,7 +115,7 @@
       // When not coming from DEX input we require interfaces to be abstract - except for
       // package-info classes - as both old versions of javac and other tools can produce
       // package-info classes that are interfaces but not abstract.
-      if (version.isGreaterThanOrEqual(Constants.CORRESPONDING_CLASS_FILE_VERSION)
+      if (version.isGreaterThan(Constants.CORRESPONDING_CLASS_FILE_VERSION)
           && !isAbstract()
           && !isPackageInfo) {
         return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 9757af4..a0d4cbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -250,6 +250,11 @@
     return factory.isConstructor(this);
   }
 
+  public DexMethod withExtraArgumentPrepended(DexType type, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createMethod(
+        holder, dexItemFactory.prependTypeToProto(type, proto), name);
+  }
+
   public DexMethod withHolder(DexType holder, DexItemFactory dexItemFactory) {
     return dexItemFactory.createMethod(holder, proto, name);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 3295733..54d6795 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -218,6 +218,12 @@
       return self();
     }
 
+    public Builder addLibraryClasses(Collection<DexLibraryClass> classes) {
+      libraryClasses =
+          ImmutableList.<DexLibraryClass>builder().addAll(libraryClasses).addAll(classes).build();
+      return self();
+    }
+
     @Override
     public DirectMappedDexApplication build() {
       // Rebuild the map. This will fail if keys are not unique.
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index 5457311..82f34d7 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -32,6 +32,10 @@
     this.enclosingMethod = enclosingMethod;
   }
 
+  public static EnclosingMethodAttribute none() {
+    return null;
+  }
+
   public void write(ClassWriter writer, NamingLens lens) {
     if (enclosingMethod != null) {
       writer.visitOuterClass(
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index f3195c9..d54bc7b 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collections;
+import java.util.List;
 import java.util.function.Consumer;
 import org.objectweb.asm.ClassWriter;
 
@@ -39,6 +41,10 @@
     this.innerName = innerName;
   }
 
+  public static List<InnerClassAttribute> emptyList() {
+    return Collections.emptyList();
+  }
+
   public void forEachType(Consumer<DexType> consumer) {
     if (inner != null) {
       consumer.accept(inner);
diff --git a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
index 96fb82b..e007f4e 100644
--- a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
@@ -19,6 +19,10 @@
     return nestHost;
   }
 
+  public static NestHostClassAttribute none() {
+    return null;
+  }
+
   public void write(ClassWriter writer, NamingLens lens) {
     assert nestHost != null;
     writer.visitNestHost(lens.lookupInternalName(nestHost));
diff --git a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
index f88a39f..f9d1a35 100644
--- a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.naming.NamingLens;
+import java.util.Collections;
+import java.util.List;
 import org.objectweb.asm.ClassWriter;
 
 public class NestMemberClassAttribute {
@@ -15,6 +17,10 @@
     this.nestMember = nestMember;
   }
 
+  public static List<NestMemberClassAttribute> emptyList() {
+    return Collections.emptyList();
+  }
+
   public DexType getNestMember() {
     return nestMember;
   }
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 c271af1..960dea0 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
@@ -48,7 +48,6 @@
 import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor;
@@ -214,13 +213,18 @@
             .map(options.itemFactory::createString)
             .collect(Collectors.toList());
     if (options.isDesugaredLibraryCompilation()) {
-      // Specific L8 Settings.
-      // DesugaredLibraryRetargeter is needed for retarget core library members and backports.
-      // InterfaceMethodRewriter is needed for emulated interfaces.
-      // LambdaRewriter is needed because if it is missing there are invoke custom on
-      // default/static interface methods, and this is not supported by the compiler.
-      // DesugaredLibraryAPIConverter is here to duplicate APIs.
-      // The rest is nulled out. In addition the rewriting logic fails without lambda rewriting.
+      // Specific L8 Settings, performs all desugaring including L8 specific desugaring.
+      // The following desugaring are required for L8 specific desugaring:
+      // - DesugaredLibraryRetargeter for retarget core library members.
+      // - InterfaceMethodRewriter for emulated interfaces,
+      // - LambdaRewriter since InterfaceMethodDesugaring does not support invokeCustom rewriting,
+      // - DesugaredLibraryAPIConverter to duplicate APIs.
+      // The following desugaring are present so all desugaring is performed cf to cf in L8, and
+      // the second L8 phase can just run with Desugar turned off:
+      // - InterfaceMethodRewriter for non L8 specific interface method desugaring,
+      // - TwrCloseResourceRewriter,
+      // - NestBaseAccessDesugaring.
+      assert options.desugarState == DesugarState.ON;
       this.desugaredLibraryRetargeter =
           options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
               ? null
@@ -232,11 +236,11 @@
       this.lambdaRewriter = new LambdaRewriter(appView);
       this.desugaredLibraryAPIConverter =
           new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
-      this.backportedMethodRewriter =
-          options.cfToCfDesugar || options.testing.forceLibBackportsInL8CfToCf
-              ? new BackportedMethodRewriter(appView)
-              : null;
-      this.twrCloseResourceRewriter = null;
+      this.backportedMethodRewriter = new BackportedMethodRewriter(appView);
+      this.twrCloseResourceRewriter =
+          enableTwrCloseResourceDesugaring() ? new TwrCloseResourceRewriter(appView, this) : null;
+      this.d8NestBasedAccessDesugaring =
+          options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
       this.lambdaMerger = null;
       this.covariantReturnTypeAnnotationTransformer = null;
       this.dynamicTypeOptimization = null;
@@ -251,7 +255,6 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
       this.typeChecker = null;
-      this.d8NestBasedAccessDesugaring = null;
       this.stringSwitchRemover = null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
@@ -552,13 +555,6 @@
       return true;
     }
     if (options.isDesugaredLibraryCompilation()) {
-      // TODO(b/169035524): Create method for evaluating if a code object needs rewriting for
-      // library desugaring.
-      return true;
-    }
-    if (options.desugaredLibraryConfiguration != DesugaredLibraryConfiguration.empty()) {
-      // TODO(b/169035524): Create method for evaluating if a code object needs rewriting for
-      // library desugaring.
       return true;
     }
 
@@ -569,14 +565,21 @@
       return true;
     }
 
-    NeedsIRDesugarUseRegistry useRegistry =
-        new NeedsIRDesugarUseRegistry(appView, backportedMethodRewriter);
-    method.registerCodeReferences(useRegistry);
-
-    if (useRegistry.needsDesugaring()) {
+    if (desugaredLibraryAPIConverter != null
+        && desugaredLibraryAPIConverter.shouldRegisterCallback(method)) {
       return true;
     }
-    return false;
+
+    NeedsIRDesugarUseRegistry useRegistry =
+        new NeedsIRDesugarUseRegistry(
+            appView,
+            backportedMethodRewriter,
+            desugaredLibraryRetargeter,
+            interfaceMethodRewriter,
+            desugaredLibraryAPIConverter);
+    method.registerCodeReferences(useRegistry);
+
+    return useRegistry.needsDesugaring();
   }
 
   private void checkPrefixMerging(ProgramMethod method) {
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 0512e69..8e1ee32 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
@@ -89,6 +89,7 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -594,7 +595,7 @@
       if (definition != null) {
         DexClassAndField field = DexClassAndField.create(holder, definition);
         if (AccessControl.isMemberAccessible(field, holder, context, appView).isTrue()) {
-          return lookup.getReboundReference();
+          return MemberRebindingAnalysis.validMemberRebindingTargetFor(appView, field, reference);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index 9d88a1f..f6ad5f3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -11,6 +11,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 
 class NeedsIRDesugarUseRegistry extends UseRegistry {
@@ -18,12 +21,22 @@
   private boolean needsDesugarging = false;
   private final AppView appView;
   private final BackportedMethodRewriter backportedMethodRewriter;
+  private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
+  private final InterfaceMethodRewriter interfaceMethodRewriter;
+  private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
 
   public NeedsIRDesugarUseRegistry(
-      AppView appView, BackportedMethodRewriter backportedMethodRewriter) {
+      AppView appView,
+      BackportedMethodRewriter backportedMethodRewriter,
+      DesugaredLibraryRetargeter desugaredLibraryRetargeter,
+      InterfaceMethodRewriter interfaceMethodRewriter,
+      DesugaredLibraryAPIConverter desugaredLibraryAPIConverter) {
     super(appView.dexItemFactory());
     this.appView = appView;
     this.backportedMethodRewriter = backportedMethodRewriter;
+    this.desugaredLibraryRetargeter = desugaredLibraryRetargeter;
+    this.interfaceMethodRewriter = interfaceMethodRewriter;
+    this.desugaredLibraryAPIConverter = desugaredLibraryAPIConverter;
   }
 
   public boolean needsDesugaring() {
@@ -31,31 +44,75 @@
   }
 
   @Override
-  public void registerInitClass(DexType type) {}
+  public void registerInitClass(DexType type) {
+    if (!needsDesugarging
+        && desugaredLibraryAPIConverter != null
+        && desugaredLibraryAPIConverter.canConvert(type)) {
+      needsDesugarging = true;
+    }
+  }
 
   @Override
   public void registerInvokeVirtual(DexMethod method) {
-    if (backportedMethodRewriter.needsDesugaring(method)) {
-      needsDesugarging = true;
-    }
+    registerBackportedMethodRewriting(method);
+    registerLibraryRetargeting(method, false);
+    registerInterfaceMethodRewriting(method);
+    registerDesugaredLibraryAPIConverter(method);
   }
 
   @Override
-  public void registerInvokeDirect(DexMethod method) {}
+  public void registerInvokeDirect(DexMethod method) {
+    registerLibraryRetargeting(method, false);
+    registerInterfaceMethodRewriting(method);
+    registerDesugaredLibraryAPIConverter(method);
+  }
+
+  private void registerBackportedMethodRewriting(DexMethod method) {
+    if (!needsDesugarging) {
+      needsDesugarging = backportedMethodRewriter.needsDesugaring(method);
+    }
+  }
+
+  private void registerInterfaceMethodRewriting(DexMethod method) {
+    if (!needsDesugarging) {
+      needsDesugarging =
+          interfaceMethodRewriter != null && interfaceMethodRewriter.needsRewriting(method);
+    }
+  }
+
+  private void registerDesugaredLibraryAPIConverter(DexMethod method) {
+    if (!needsDesugarging) {
+      needsDesugarging =
+          desugaredLibraryAPIConverter != null
+              && desugaredLibraryAPIConverter.shouldRewriteInvoke(method);
+    }
+  }
+
+  private void registerLibraryRetargeting(DexMethod method, boolean b) {
+    if (!needsDesugarging) {
+      needsDesugarging =
+          desugaredLibraryRetargeter != null
+              && desugaredLibraryRetargeter.getRetargetedMethod(method, b) != null;
+    }
+  }
 
   @Override
   public void registerInvokeStatic(DexMethod method) {
-    if (TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView)) {
-      needsDesugarging = true;
+    if (!needsDesugarging) {
+      needsDesugarging = TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView);
     }
-
-    if (backportedMethodRewriter.needsDesugaring(method)) {
-      needsDesugarging = true;
-    }
+    registerBackportedMethodRewriting(method);
+    registerLibraryRetargeting(method, false);
+    registerInterfaceMethodRewriting(method);
+    registerDesugaredLibraryAPIConverter(method);
   }
 
   @Override
-  public void registerInvokeInterface(DexMethod method) {}
+  public void registerInvokeInterface(DexMethod method) {
+    registerLibraryRetargeting(method, true);
+    registerInterfaceMethodRewriting(method);
+    registerDesugaredLibraryAPIConverter(method);
+  }
 
   @Override
   public void registerInvokeStatic(DexMethod method, boolean itf) {
@@ -72,7 +129,11 @@
   }
 
   @Override
-  public void registerInvokeSuper(DexMethod method) {}
+  public void registerInvokeSuper(DexMethod method) {
+    registerLibraryRetargeting(method, false);
+    registerInterfaceMethodRewriting(method);
+    registerDesugaredLibraryAPIConverter(method);
+  }
 
   @Override
   public void registerInstanceFieldRead(DexField field) {}
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 04fb914..11e6e33 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
@@ -75,7 +75,7 @@
   }
 
   public boolean needsDesugaring(DexMethod method) {
-    return rewritableMethods.getProvider(method) != null;
+    return getMethodProviderOrNull(method) != null;
   }
 
   public static List<DexMethod> generateListOfBackportedMethods(
@@ -165,7 +165,30 @@
   private MethodProvider getMethodProviderOrNull(DexMethod method) {
     DexMethod original = appView.graphLens().getOriginalMethodSignature(method);
     assert original != null;
-    return rewritableMethods.getProvider(original);
+    MethodProvider provider = rewritableMethods.getProvider(original);
+    // TODO(b/150693139): Since the DesugarLibraryRetargeter is run during IR processing while the
+    // backported method rewriter is run in cf to cf, we need here to compute if the method is
+    // actually going to be retargeted through desugared library backports, and compute the
+    // corresponding backported method if so. This can be removed once the DesugarLibraryRetargeter
+    // has been moved as a cf to cf transformation.
+    if (provider == null
+        && appView.options().isDesugaredLibraryCompilation()
+        && appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getBackportCoreLibraryMember()
+            .containsKey(method.holder)) {
+      DexType newHolder =
+          appView
+              .options()
+              .desugaredLibraryConfiguration
+              .getBackportCoreLibraryMember()
+              .get(method.holder);
+      DexMethod backportedMethod =
+          appView.dexItemFactory().createMethod(newHolder, method.proto, method.name);
+      provider = rewritableMethods.getProvider(backportedMethod);
+    }
+    return provider;
   }
 
   private static final class RewritableMethods {
@@ -175,15 +198,6 @@
 
     RewritableMethods(InternalOptions options, AppView<?> appView) {
 
-      if (options.testing.forceLibBackportsInL8CfToCf) {
-        DexItemFactory factory = options.itemFactory;
-        initializeJava9OptionalMethodProviders(factory);
-        initializeJava10OptionalMethodProviders(factory);
-        initializeJava11OptionalMethodProviders(factory);
-        initializeStreamMethodProviders(factory);
-        return;
-      }
-
       if (!options.shouldBackportMethods()) {
         return;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index bd468b0..787915f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -146,6 +146,9 @@
   public void desugarNestBasedAccess(
       DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
       throws ExecutionException {
+    if (metNestHosts.isEmpty()) {
+      return;
+    }
     processNestsConcurrently(executorService);
     addDeferredBridges();
     synthesizeNestConstructor(builder);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 401297e..c337afe 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -146,7 +146,7 @@
     return true;
   }
 
-  private boolean shouldRewriteInvoke(DexMethod invokedMethod) {
+  public boolean shouldRewriteInvoke(DexMethod invokedMethod) {
     if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView)
         || invokedMethod.holder.isArrayType()) {
       return false;
@@ -164,7 +164,7 @@
     }
   }
 
-  private boolean shouldRegisterCallback(ProgramMethod method) {
+  public boolean shouldRegisterCallback(ProgramMethod method) {
     // Any override of a library method can be called by the library.
     // We duplicate the method to have a vivified type version callable by the library and
     // a type version callable by the program. We need to add the vivified version to the rootset
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 68a2df3..904ca7e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -22,10 +24,15 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.code.IRCode;
@@ -42,9 +49,12 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
@@ -84,6 +94,109 @@
     }
   }
 
+  public static void amendLibraryWithRetargetedMembers(AppView<AppInfoWithClassHierarchy> appView) {
+    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+    Map<DexType, DexLibraryClass> synthesizedLibraryClasses =
+        synthesizeLibraryClassesForRetargetedMembers(appView, retargetCoreLibMember);
+    Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedLibraryMethods =
+        synthesizedMembersForRetargetClasses(
+            appView, retargetCoreLibMember, synthesizedLibraryClasses);
+    synthesizedLibraryMethods.forEach(DexLibraryClass::addDirectMethods);
+    DirectMappedDexApplication newApplication =
+        appView
+            .appInfo()
+            .app()
+            .asDirect()
+            .builder()
+            .addLibraryClasses(synthesizedLibraryClasses.values())
+            .build();
+    appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(app -> newApplication));
+  }
+
+  private static Map<DexType, DexLibraryClass> synthesizeLibraryClassesForRetargetedMembers(
+      AppView<AppInfoWithClassHierarchy> appView,
+      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    Map<DexType, DexLibraryClass> synthesizedLibraryClasses = new LinkedHashMap<>();
+    for (Map<DexType, DexType> oldToNewTypeMap : retargetCoreLibMember.values()) {
+      for (DexType newType : oldToNewTypeMap.values()) {
+        if (appView.definitionFor(newType) == null) {
+          synthesizedLibraryClasses.computeIfAbsent(
+              newType,
+              type ->
+                  // Synthesize a library class with the given name. Note that this is assuming that
+                  // the library class inherits directly from java.lang.Object, does not implement
+                  // any interfaces, etc.
+                  new DexLibraryClass(
+                      type,
+                      Kind.CF,
+                      new SynthesizedOrigin(
+                          "Desugared library retargeter", DesugaredLibraryRetargeter.class),
+                      ClassAccessFlags.fromCfAccessFlags(Constants.ACC_PUBLIC),
+                      dexItemFactory.objectType,
+                      DexTypeList.empty(),
+                      dexItemFactory.createString("DesugaredLibraryRetargeter"),
+                      NestHostClassAttribute.none(),
+                      NestMemberClassAttribute.emptyList(),
+                      EnclosingMethodAttribute.none(),
+                      InnerClassAttribute.emptyList(),
+                      ClassSignature.noSignature(),
+                      DexAnnotationSet.empty(),
+                      DexEncodedField.EMPTY_ARRAY,
+                      DexEncodedField.EMPTY_ARRAY,
+                      DexEncodedMethod.EMPTY_ARRAY,
+                      DexEncodedMethod.EMPTY_ARRAY,
+                      dexItemFactory.getSkipNameValidationForTesting()));
+        }
+      }
+    }
+    return synthesizedLibraryClasses;
+  }
+
+  private static Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembersForRetargetClasses(
+      AppView<AppInfoWithClassHierarchy> appView,
+      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
+      Map<DexType, DexLibraryClass> synthesizedLibraryClasses) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembers = new IdentityHashMap<>();
+    for (Entry<DexString, Map<DexType, DexType>> entry : retargetCoreLibMember.entrySet()) {
+      DexString methodName = entry.getKey();
+      Map<DexType, DexType> types = entry.getValue();
+      types.forEach(
+          (oldType, newType) -> {
+            DexClass oldClass = appView.definitionFor(oldType);
+            DexLibraryClass newClass = synthesizedLibraryClasses.get(newType);
+            if (oldClass == null || newClass == null) {
+              return;
+            }
+            for (DexEncodedMethod method :
+                oldClass.methods(method -> method.getName() == methodName)) {
+              DexMethod retargetMethod = method.getReference().withHolder(newType, dexItemFactory);
+              if (!method.isStatic()) {
+                retargetMethod = retargetMethod.withExtraArgumentPrepended(oldType, dexItemFactory);
+              }
+              synthesizedMembers
+                  .computeIfAbsent(
+                      newClass,
+                      ignore ->
+                          new TreeSet<>((x, y) -> x.getReference().slowCompareTo(y.getReference())))
+                  .add(
+                      new DexEncodedMethod(
+                          retargetMethod,
+                          MethodAccessFlags.fromCfAccessFlags(
+                              Constants.ACC_PUBLIC | Constants.ACC_STATIC, false),
+                          MethodTypeSignature.noSignature(),
+                          DexAnnotationSet.empty(),
+                          ParameterAnnotationsList.empty(),
+                          null,
+                          true));
+            }
+          });
+    }
+    return synthesizedMembers;
+  }
+
   private static void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
     StringDiagnostic warning =
         new StringDiagnostic(
@@ -155,25 +268,12 @@
       }
 
       InvokeMethod invoke = instruction.asInvokeMethod();
-      DexMethod retarget = getRetargetLibraryMember(invoke.getInvokedMethod());
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      boolean isInterface = invoke.getInterfaceBit();
+
+      DexMethod retarget = getRetargetedMethod(invokedMethod, isInterface);
       if (retarget == null) {
-        if (!matchesNonFinalHolderRewrite(invoke.getInvokedMethod())) {
-          continue;
-        }
-        // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
-        ResolutionResult resolutionResult =
-            appView
-                .appInfoForDesugaring()
-                .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit());
-        if (resolutionResult.isFailedResolution()) {
-          continue;
-        }
-        DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
-        assert singleTarget != null;
-        retarget = getRetargetLibraryMember(singleTarget.method);
-        if (retarget == null) {
-          continue;
-        }
+        continue;
       }
 
       // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
@@ -203,6 +303,28 @@
     }
   }
 
+  public DexMethod getRetargetedMethod(DexMethod invokedMethod, boolean isInterface) {
+    DexMethod retarget = getRetargetLibraryMember(invokedMethod);
+    if (retarget == null) {
+      if (!matchesNonFinalHolderRewrite(invokedMethod)) {
+        return null;
+      }
+      // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
+      ResolutionResult resolutionResult =
+          appView.appInfoForDesugaring().resolveMethod(invokedMethod, isInterface);
+      if (resolutionResult.isFailedResolution()) {
+        return null;
+      }
+      DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+      assert singleTarget != null;
+      retarget = getRetargetLibraryMember(singleTarget.method);
+      if (retarget == null) {
+        return null;
+      }
+    }
+    return retarget;
+  }
+
   private DexMethod getRetargetLibraryMember(DexMethod method) {
     Map<DexType, DexType> backportCoreLibraryMembers =
         appView.options().desugaredLibraryConfiguration.getBackportCoreLibraryMember();
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 ee05401..4a3ee89 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
@@ -209,6 +209,10 @@
     return emulatedInterfaces.containsKey(itf);
   }
 
+  public boolean needsRewriting(DexMethod method) {
+    return emulatedMethods.contains(method.getName());
+  }
+
   DexType getEmulatedInterface(DexType itf) {
     return emulatedInterfaces.get(itf);
   }
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 f172fa9..6f945bd 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
@@ -27,64 +27,25 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 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;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 
 public final class ClassInliner {
 
   enum EligibilityStatus {
-    // Used by InlineCandidateProcessor#isInstanceEligible
-    NON_CLASS_TYPE,
-    NOT_A_SINGLETON_FIELD,
-    RETRIEVAL_MAY_HAVE_SIDE_EFFECTS,
-    UNKNOWN_TYPE,
-    UNUSED_INSTANCE,
-
-    // Used by isClassEligible
-    NON_PROGRAM_CLASS,
-    ABSTRACT_OR_INTERFACE,
-    NEVER_CLASS_INLINE,
-    IS_PINNED_TYPE,
-    HAS_FINALIZER,
-    TRIGGER_CLINIT,
-
-    // Used by InlineCandidateProcessor#isClassAndUsageEligible
-    HAS_CLINIT,
-    HAS_INSTANCE_FIELDS,
-    NON_FINAL_TYPE,
-    NOT_INITIALIZED_AT_INIT,
-    PINNED_FIELD,
-
-    ELIGIBLE
+    ELIGIBLE,
+    NOT_ELIGIBLE
   }
 
   private final ConcurrentHashMap<DexClass, EligibilityStatus> knownClasses =
       new ConcurrentHashMap<>();
 
-  private void logEligibilityStatus(
-      DexEncodedMethod context, Instruction root, EligibilityStatus status) {
-    if (Log.ENABLED && Log.isLoggingEnabledFor(ClassInliner.class)) {
-      Log.info(getClass(), "At %s,", context.toSourceString());
-      Log.info(getClass(), "ClassInlining eligibility of `%s`: %s.", root, status);
-    }
-  }
-
-  private void logIneligibleUser(
-      DexEncodedMethod context, Instruction root, InstructionOrPhi ineligibleUser) {
-    if (Log.ENABLED && Log.isLoggingEnabledFor(ClassInliner.class)) {
-      Log.info(getClass(), "At %s,", context.toSourceString());
-      Log.info(getClass(), "Ineligible user of `%s`: `%s`.", root, ineligibleUser);
-    }
-  }
-
   // Process method code and inline eligible class instantiations, in short:
   //
   // - collect all 'new-instance' and 'static-get' instructions (called roots below) in
@@ -176,9 +137,7 @@
 
     // Collect all the new-instance and static-get instructions in the code before inlining.
     List<Instruction> roots =
-        Streams.stream(code.instructionIterator())
-            .filter(insn -> insn.isNewInstance() || insn.isStaticGet())
-            .collect(Collectors.toList());
+        Lists.newArrayList(code.instructions(insn -> insn.isNewInstance() || insn.isStaticGet()));
 
     // We loop inlining iterations until there was no inlining, but still use same set
     // of roots to avoid infinite inlining. Looping makes possible for some roots to
@@ -204,13 +163,11 @@
         // Assess eligibility of instance and class.
         EligibilityStatus status = processor.isInstanceEligible();
         if (status != EligibilityStatus.ELIGIBLE) {
-          logEligibilityStatus(code.method(), root, status);
           // This root will never be inlined.
           rootsIterator.remove();
           continue;
         }
         status = processor.isClassAndUsageEligible();
-        logEligibilityStatus(code.method(), root, status);
         if (status != EligibilityStatus.ELIGIBLE) {
           // This root will never be inlined.
           rootsIterator.remove();
@@ -221,7 +178,6 @@
         InstructionOrPhi ineligibleUser = processor.areInstanceUsersEligible(defaultOracle);
         if (ineligibleUser != null) {
           // This root may succeed if users change in future.
-          logIneligibleUser(code.method(), root, ineligibleUser);
           continue;
         }
 
@@ -309,7 +265,8 @@
     }
   }
 
-  private EligibilityStatus isClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) {
+  private EligibilityStatus isClassEligible(
+      AppView<AppInfoWithLiveness> appView, DexProgramClass clazz) {
     EligibilityStatus eligible = knownClasses.get(clazz);
     if (eligible == null) {
       EligibilityStatus computed = computeClassEligible(appView, clazz);
@@ -325,21 +282,12 @@
   //   - does not declare finalizer
   //   - does not trigger any static initializers except for its own
   private EligibilityStatus computeClassEligible(
-      AppView<AppInfoWithLiveness> appView, DexClass clazz) {
-    if (clazz == null) {
-      return EligibilityStatus.UNKNOWN_TYPE;
-    }
-    if (clazz.isNotProgramClass()) {
-      return EligibilityStatus.NON_PROGRAM_CLASS;
-    }
-    if (clazz.isAbstract() || clazz.isInterface()) {
-      return EligibilityStatus.ABSTRACT_OR_INTERFACE;
-    }
-    if (appView.appInfo().neverClassInline.contains(clazz.type)) {
-      return EligibilityStatus.NEVER_CLASS_INLINE;
-    }
-    if (appView.appInfo().isPinned(clazz.type)) {
-      return EligibilityStatus.IS_PINNED_TYPE;
+      AppView<AppInfoWithLiveness> appView, DexProgramClass clazz) {
+    if (clazz == null
+        || clazz.isAbstract()
+        || clazz.isInterface()
+        || !appView.appInfo().isClassInliningAllowed(clazz)) {
+      return EligibilityStatus.NOT_ELIGIBLE;
     }
 
     // Class must not define finalizer.
@@ -347,13 +295,13 @@
     for (DexEncodedMethod method : clazz.virtualMethods()) {
       if (method.method.name == dexItemFactory.finalizeMethodName
           && method.method.proto == dexItemFactory.objectMembers.finalize.proto) {
-        return EligibilityStatus.HAS_FINALIZER;
+        return EligibilityStatus.NOT_ELIGIBLE;
       }
     }
 
     // Check for static initializers in this class or any of interfaces it implements.
     if (clazz.initializationOfParentTypesMayHaveSideEffects(appView)) {
-      return EligibilityStatus.TRIGGER_CLINIT;
+      return EligibilityStatus.NOT_ELIGIBLE;
     }
     return EligibilityStatus.ELIGIBLE;
   }
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 b917507..c8cc00e 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
@@ -12,7 +12,6 @@
 import com.android.tools.r8.errors.Unreachable;
 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.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -95,7 +94,7 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory dexItemFactory;
   private final Inliner inliner;
-  private final Function<DexClass, EligibilityStatus> isClassEligible;
+  private final Function<DexProgramClass, EligibilityStatus> isClassEligible;
   private final MethodProcessor methodProcessor;
   private final ProgramMethod method;
   private final Instruction root;
@@ -122,7 +121,7 @@
   InlineCandidateProcessor(
       AppView<AppInfoWithLiveness> appView,
       Inliner inliner,
-      Function<DexClass, EligibilityStatus> isClassEligible,
+      Function<DexProgramClass, EligibilityStatus> isClassEligible,
       MethodProcessor methodProcessor,
       ProgramMethod method,
       Instruction root) {
@@ -157,20 +156,20 @@
   EligibilityStatus isInstanceEligible() {
     eligibleInstance = root.outValue();
     if (eligibleInstance == null) {
-      return EligibilityStatus.UNUSED_INSTANCE;
+      return EligibilityStatus.NOT_ELIGIBLE;
     }
 
     if (root.isNewInstance()) {
       eligibleClass = asProgramClassOrNull(appView.definitionFor(root.asNewInstance().clazz));
       if (eligibleClass == null) {
-        return EligibilityStatus.UNKNOWN_TYPE;
+        return EligibilityStatus.NOT_ELIGIBLE;
       }
       if (eligibleClass.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of the current context are guaranteed to be initialized.
           type -> appView.isSubtype(method.getHolderType(), type).isTrue(),
           Sets.newIdentityHashSet())) {
-        return EligibilityStatus.HAS_CLINIT;
+        return EligibilityStatus.NOT_ELIGIBLE;
       }
       return EligibilityStatus.ELIGIBLE;
     }
@@ -179,19 +178,19 @@
 
     StaticGet staticGet = root.asStaticGet();
     if (staticGet.instructionMayHaveSideEffects(appView, method)) {
-      return EligibilityStatus.RETRIEVAL_MAY_HAVE_SIDE_EFFECTS;
+      return EligibilityStatus.NOT_ELIGIBLE;
     }
     DexEncodedField field = appView.appInfo().resolveField(staticGet.getField()).getResolvedField();
     FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
     ClassTypeElement dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
     if (dynamicLowerBoundType == null
         || !dynamicLowerBoundType.equals(optimizationInfo.getDynamicUpperBoundType())) {
-      return EligibilityStatus.NOT_A_SINGLETON_FIELD;
+      return EligibilityStatus.NOT_ELIGIBLE;
     }
     eligibleClass =
         asProgramClassOrNull(appView.definitionFor(dynamicLowerBoundType.getClassType()));
     if (eligibleClass == null) {
-      return EligibilityStatus.UNKNOWN_TYPE;
+      return EligibilityStatus.NOT_ELIGIBLE;
     }
     return EligibilityStatus.ELIGIBLE;
   }
@@ -313,7 +312,7 @@
 
           if (AccessControl.isClassAccessible(singleTarget.getHolder(), method, appView)
               .isPossiblyFalse()) {
-            continue;
+            return user; // Not eligible.
           }
 
           // Eligible constructor call (for new instance roots only).
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 9d4121c..fb120e0 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -5,7 +5,8 @@
 
 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.DexClassAndField;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -48,7 +49,7 @@
   }
 
   private DexMethod validTargetFor(DexMethod target, DexMethod original) {
-    DexClass clazz = appView.definitionFor(target.holder);
+    DexClass clazz = appView.definitionFor(target.getHolderType());
     assert clazz != null;
     if (clazz.isProgramClass()) {
       return target;
@@ -56,36 +57,38 @@
     DexType newHolder;
     if (clazz.isInterface()) {
       newHolder =
-          firstLibraryClassForInterfaceTarget(target, original.holder, DexClass::lookupMethod);
+          firstLibraryClassForInterfaceTarget(
+              appView, target, original.holder, DexClass::lookupMethod);
     } else {
-      newHolder = firstLibraryClass(target.holder, original.holder);
+      newHolder = firstLibraryClass(appView, target.getHolderType(), original.getHolderType());
     }
     return newHolder == null
         ? original
         : appView.dexItemFactory().createMethod(newHolder, original.proto, original.name);
   }
 
-  private DexField validTargetFor(DexField target, DexField original,
-      BiFunction<DexClass, DexField, DexEncodedField> lookup) {
-    DexClass clazz = appView.definitionFor(target.holder);
-    assert clazz != null;
-    if (clazz.isProgramClass()) {
-      return target;
+  public static DexField validMemberRebindingTargetFor(
+      DexDefinitionSupplier definitions, DexClassAndField field, DexField original) {
+    DexClass clazz = field.getHolder();
+    if (field.isProgramField()) {
+      return field.getReference();
     }
-    DexType newHolder;
-    if (clazz.isInterface()) {
-      newHolder = firstLibraryClassForInterfaceTarget(target, original.holder, lookup);
-    } else {
-      newHolder = firstLibraryClass(target.holder, original.holder);
-    }
-    return newHolder == null
-        ? original
-        : appView.dexItemFactory().createField(newHolder, original.type, original.name);
+    DexType newHolder =
+        clazz.isInterface()
+            ? firstLibraryClassForInterfaceTarget(
+                definitions, field.getReference(), original.getHolderType(), DexClass::lookupField)
+            : firstLibraryClass(definitions, field.getHolderType(), original.getHolderType());
+    return newHolder != null
+        ? field.getReference().withHolder(newHolder, definitions.dexItemFactory())
+        : original;
   }
 
-  private <T> DexType firstLibraryClassForInterfaceTarget(T target, DexType current,
+  private static <T> DexType firstLibraryClassForInterfaceTarget(
+      DexDefinitionSupplier definitions,
+      T target,
+      DexType current,
       BiFunction<DexClass, T, ?> lookup) {
-    DexClass clazz = appView.definitionFor(current);
+    DexClass clazz = definitions.definitionFor(current);
     if (clazz == null) {
       return null;
     }
@@ -95,14 +98,16 @@
       return current;
     }
     if (clazz.superType != null) {
-      DexType matchingSuper = firstLibraryClassForInterfaceTarget(target, clazz.superType, lookup);
+      DexType matchingSuper =
+          firstLibraryClassForInterfaceTarget(definitions, target, clazz.superType, lookup);
       if (matchingSuper != null) {
         // Found in supertype, return first library class.
         return clazz.isNotProgramClass() ? current : matchingSuper;
       }
     }
     for (DexType iface : clazz.interfaces.values) {
-      DexType matchingIface = firstLibraryClassForInterfaceTarget(target, iface, lookup);
+      DexType matchingIface =
+          firstLibraryClassForInterfaceTarget(definitions, target, iface, lookup);
       if (matchingIface != null) {
         // Found in interface, return first library class.
         return clazz.isNotProgramClass() ? current : matchingIface;
@@ -111,11 +116,12 @@
     return null;
   }
 
-  private DexType firstLibraryClass(DexType top, DexType bottom) {
-    assert appView.definitionFor(top).isNotProgramClass();
-    DexClass searchClass = appView.definitionFor(bottom);
+  private static DexType firstLibraryClass(
+      DexDefinitionSupplier definitions, DexType top, DexType bottom) {
+    assert definitions.definitionFor(top).isNotProgramClass();
+    DexClass searchClass = definitions.definitionFor(bottom);
     while (searchClass.isProgramClass()) {
-      searchClass = appView.definitionFor(searchClass.superType);
+      searchClass = definitions.definitionFor(searchClass.superType);
     }
     return searchClass.type;
   }
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
index 8820838..a5a77d7 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
@@ -241,7 +241,6 @@
     for (DexProgramClass clazz : synthesizedFrom) {
       // TODO(b/165783399): What do we want to put here if the class that this was synthesized from
       //  is no longer in the application?
-      assert appView.definitionFor(clazz.type) != null;
       DexProgramClass newClass =
           newProgramClasses.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
       newSynthesizedFrom.add(newClass);
diff --git a/src/main/java/com/android/tools/r8/retrace/Result.java b/src/main/java/com/android/tools/r8/retrace/Result.java
deleted file mode 100644
index 692ca9b..0000000
--- a/src/main/java/com/android/tools/r8/retrace/Result.java
+++ /dev/null
@@ -1,19 +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.retrace;
-
-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);
-
-  public abstract boolean isAmbiguous();
-}
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 b8d94e6..9b838df 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -12,6 +12,13 @@
 import com.android.tools.r8.Version;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
 import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
+import com.android.tools.r8.retrace.internal.PlainStackTraceVisitor;
+import com.android.tools.r8.retrace.internal.RetraceAbortException;
+import com.android.tools.r8.retrace.internal.RetraceCommandLineResult;
+import com.android.tools.r8.retrace.internal.RetraceRegularExpression;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
+import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl;
+import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
@@ -157,8 +164,8 @@
     try {
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       timing.begin("Read proguard map");
-      RetraceApi retracer =
-          Retracer.create(command.proguardMapProducer, command.diagnosticsHandler);
+      RetracerImpl retracer =
+          RetracerImpl.create(command.proguardMapProducer, command.diagnosticsHandler);
       timing.end();
       RetraceCommandLineResult result;
       timing.begin("Parse and Retrace");
@@ -175,7 +182,7 @@
         PlainStackTraceVisitor plainStackTraceVisitor =
             new PlainStackTraceVisitor(command.stackTrace, command.diagnosticsHandler);
         StackTraceElementProxyRetracer<StackTraceElementStringProxy> proxyRetracer =
-            new StackTraceElementProxyRetracer<>(retracer);
+            new StackTraceElementProxyRetracerImpl<>(retracer);
         List<String> retracedStrings = new ArrayList<>();
         plainStackTraceVisitor.forEach(
             stackTraceElement -> {
@@ -274,8 +281,6 @@
     return readLines;
   }
 
-  static class RetraceAbortException extends RuntimeException {}
-
   private interface MainAction {
     void run() throws RetraceAbortException;
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceApi.java b/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
deleted file mode 100644
index 7a1eb4e..0000000
--- a/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
+++ /dev/null
@@ -1,28 +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.retrace;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.FieldReference;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.TypeReference;
-
-/** This is the main api interface for retrace. */
-@Keep
-public interface RetraceApi {
-
-  // TODO(b/170711681): Rename these to not have overloads.
-
-  RetraceClassResult retrace(ClassReference classReference);
-
-  RetraceMethodResult retrace(MethodReference methodReference);
-
-  RetraceFieldResult retrace(FieldReference fieldReference);
-
-  RetraceFrameResult retrace(MethodReference methodReference, int position);
-
-  RetraceTypeResult retrace(TypeReference typeReference);
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassMemberElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassMemberElement.java
deleted file mode 100644
index c21a100..0000000
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassMemberElement.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.retrace;
-
-import java.util.function.BiConsumer;
-
-public interface RetraceClassMemberElement<T extends RetracedClassMember> {
-
-  boolean isUnknown();
-
-  default boolean isFrameElement() {
-    return false;
-  }
-
-  default RetraceFrameResult.Element asFrameElement() {
-    return null;
-  }
-
-  RetraceClassResult.Element getClassElement();
-
-  T getMember();
-
-  void visitFrames(BiConsumer<T, Integer> consumer);
-
-  RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile);
-}
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 795311e..f9d0d6f 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -1,244 +1,58 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.naming.MemberNaming.NoSignature.NO_SIGNATURE;
-import static com.android.tools.r8.retrace.RetraceUtils.synthesizeFileName;
-
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.naming.ClassNamingForNameMapper;
-import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
-import com.android.tools.r8.naming.MemberNaming;
-import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.references.ClassReference;
-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.utils.Pair;
-import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 @Keep
-public class RetraceClassResult extends Result<Element, RetraceClassResult> {
+public interface RetraceClassResult {
 
-  private final ClassReference obfuscatedReference;
-  private final ClassNamingForNameMapper mapper;
-  private final RetraceApi retracer;
+  RetraceFieldResult lookupField(String fieldName);
 
-  private RetraceClassResult(
-      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetraceApi retracer) {
-    this.obfuscatedReference = obfuscatedReference;
-    this.mapper = mapper;
-    this.retracer = retracer;
-  }
+  RetraceFieldResult lookupField(String fieldName, TypeReference fieldType);
 
-  static RetraceClassResult create(
-      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetraceApi retracer) {
-    return new RetraceClassResult(obfuscatedReference, mapper, retracer);
-  }
+  RetraceMethodResult lookupMethod(String methodName);
 
-  public RetraceFieldResult lookupField(String fieldName) {
-    return lookupField(FieldDefinition.create(obfuscatedReference, fieldName));
-  }
+  RetraceMethodResult lookupMethod(
+      String methodName, List<TypeReference> formalTypes, TypeReference returnType);
 
-  public RetraceFieldResult lookupField(String fieldName, TypeReference fieldType) {
-    return lookupField(
-        FieldDefinition.create(Reference.field(obfuscatedReference, fieldName, fieldType)));
-  }
+  RetraceFrameResult lookupFrame(String methodName);
 
-  public RetraceMethodResult lookupMethod(String methodName) {
-    return lookupMethod(MethodDefinition.create(obfuscatedReference, methodName));
-  }
+  RetraceFrameResult lookupFrame(String methodName, int position);
 
-  public RetraceMethodResult lookupMethod(
-      String methodName, List<TypeReference> formalTypes, TypeReference returnType) {
-    return lookupMethod(
-        MethodDefinition.create(
-            Reference.method(obfuscatedReference, methodName, formalTypes, returnType)));
-  }
+  RetraceFrameResult lookupFrame(
+      String methodName, int position, List<TypeReference> formalTypes, TypeReference returnType);
 
-  private RetraceFieldResult lookupField(FieldDefinition fieldDefinition) {
-    return lookup(
-        fieldDefinition,
-        (mapper, name) -> {
-          List<MemberNaming> memberNamings = mapper.mappedFieldNamingsByName.get(name);
-          if (memberNamings == null || memberNamings.isEmpty()) {
-            return null;
-          }
-          return memberNamings;
-        },
-        RetraceFieldResult::new);
-  }
+  Stream<Element> stream();
 
-  private RetraceMethodResult lookupMethod(MethodDefinition methodDefinition) {
-    return lookup(
-        methodDefinition,
-        (mapper, name) -> {
-          MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(name);
-          if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
-            return null;
-          }
-          return mappedRanges.getMappedRanges();
-        },
-        RetraceMethodResult::new);
-  }
+  RetraceClassResult forEach(Consumer<Element> resultConsumer);
 
-  private <T, R, D extends Definition> R lookup(
-      D definition,
-      BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
-      ResultConstructor<T, R, D> constructor) {
-    List<Pair<Element, T>> mappings = new ArrayList<>();
-    forEach(
-        element -> {
-          if (mapper != null) {
-            assert element.mapper != null;
-            T mappedElements = lookupFunction.apply(element.mapper, definition.getName());
-            if (mappedElements != null) {
-              mappings.add(new Pair<>(element, mappedElements));
-              return;
-            }
-          }
-          mappings.add(new Pair<>(element, null));
-        });
-    return constructor.create(this, mappings, definition, retracer);
-  }
+  boolean isAmbiguous();
 
-  boolean hasRetraceResult() {
-    return mapper != null;
-  }
+  @Keep
+  interface Element {
 
-  @Override
-  public Stream<Element> stream() {
-    return Stream.of(
-        new Element(
-            this,
-            RetracedClass.create(
-                mapper == null
-                    ? obfuscatedReference
-                    : Reference.classFromTypeName(mapper.originalName)),
-            mapper));
-  }
+    RetracedClass getRetracedClass();
 
-  @Override
-  public RetraceClassResult forEach(Consumer<Element> resultConsumer) {
-    stream().forEach(resultConsumer);
-    return this;
-  }
+    RetraceClassResult getRetraceClassResult();
 
-  private interface ResultConstructor<T, R, D> {
-    R create(
-        RetraceClassResult classResult,
-        List<Pair<Element, T>> mappings,
-        D definition,
-        RetraceApi retraceApi);
-  }
+    RetraceSourceFileResult retraceSourceFile(String sourceFile);
 
-  @Override
-  public boolean isAmbiguous() {
-    // Currently we have no way of producing ambiguous class results.
-    return false;
-  }
+    RetraceFieldResult lookupField(String fieldName);
 
-  public static class Element {
+    RetraceMethodResult lookupMethod(String methodName);
 
-    private final RetraceClassResult classResult;
-    private final RetracedClass classReference;
-    private final ClassNamingForNameMapper mapper;
+    RetraceFrameResult lookupFrame(String methodName);
 
-    public Element(
-        RetraceClassResult classResult,
-        RetracedClass classReference,
-        ClassNamingForNameMapper mapper) {
-      this.classResult = classResult;
-      this.classReference = classReference;
-      this.mapper = mapper;
-    }
+    RetraceFrameResult lookupFrame(String methodName, int position);
 
-    public RetracedClass getRetracedClass() {
-      return classReference;
-    }
-
-    public RetraceClassResult getRetraceClassResult() {
-      return classResult;
-    }
-
-    public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
-      if (mapper != null && mapper.getAdditionalMappings().size() > 0) {
-        List<MappingInformation> mappingInformations =
-            mapper.getAdditionalMappings().get(NO_SIGNATURE);
-        if (mappingInformations != null) {
-          for (MappingInformation mappingInformation : mappingInformations) {
-            if (mappingInformation.isFileNameInformation()) {
-              return new RetraceSourceFileResult(
-                  mappingInformation.asFileNameInformation().getFileName(), false);
-            }
-          }
-        }
-      }
-      return new RetraceSourceFileResult(
-          synthesizeFileName(
-              classReference.getTypeName(),
-              classResult.obfuscatedReference.getTypeName(),
-              sourceFile,
-              mapper != null),
-          true);
-    }
-
-    public RetraceFieldResult lookupField(String fieldName) {
-      return lookupField(FieldDefinition.create(classReference.getClassReference(), fieldName));
-    }
-
-    private RetraceFieldResult lookupField(FieldDefinition fieldDefinition) {
-      return lookup(
-          fieldDefinition,
-          (mapper, name) -> {
-            List<MemberNaming> memberNamings = mapper.mappedFieldNamingsByName.get(name);
-            if (memberNamings == null || memberNamings.isEmpty()) {
-              return null;
-            }
-            return memberNamings;
-          },
-          RetraceFieldResult::new);
-    }
-
-    public RetraceMethodResult lookupMethod(String methodName) {
-      return lookupMethod(MethodDefinition.create(classReference.getClassReference(), methodName));
-    }
-
-    private RetraceMethodResult lookupMethod(MethodDefinition methodDefinition) {
-      return lookup(
-          methodDefinition,
-          (mapper, name) -> {
-            MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(name);
-            if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
-              return null;
-            }
-            return mappedRanges.getMappedRanges();
-          },
-          RetraceMethodResult::new);
-    }
-
-    private <T, R, D extends Definition> R lookup(
-        D definition,
-        BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
-        ResultConstructor<T, R, D> constructor) {
-      List<Pair<Element, T>> mappings = ImmutableList.of();
-      if (mapper != null) {
-        T result = lookupFunction.apply(mapper, definition.getName());
-        if (result != null) {
-          mappings = ImmutableList.of(new Pair<>(this, result));
-        }
-      }
-      if (mappings.isEmpty()) {
-        mappings = ImmutableList.of(new Pair<>(this, null));
-      }
-      return constructor.create(classResult, mappings, definition, classResult.retracer);
-    }
+    RetraceFrameResult lookupFrame(
+        String methodName, int position, List<TypeReference> formalTypes, TypeReference returnType);
   }
 }
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 46f247d..f141080 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -1,129 +1,31 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.retrace;
 
 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.Reference;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.Pair;
-import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 @Keep
-public class RetraceFieldResult extends Result<RetraceFieldResult.Element, RetraceFieldResult> {
+public interface RetraceFieldResult {
 
-  private final RetraceClassResult classResult;
-  private final List<Pair<RetraceClassResult.Element, List<MemberNaming>>> memberNamings;
-  private final FieldDefinition fieldDefinition;
-  private final RetraceApi retracer;
+  Stream<Element> stream();
 
-  RetraceFieldResult(
-      RetraceClassResult classResult,
-      List<Pair<RetraceClassResult.Element, List<MemberNaming>>> memberNamings,
-      FieldDefinition fieldDefinition,
-      RetraceApi retracer) {
-    this.classResult = classResult;
-    this.memberNamings = memberNamings;
-    this.fieldDefinition = fieldDefinition;
-    this.retracer = retracer;
-    assert classResult != null;
-    assert !memberNamings.isEmpty();
-  }
+  RetraceFieldResult forEach(Consumer<Element> resultConsumer);
 
-  @Override
-  public boolean isAmbiguous() {
-    if (memberNamings.size() > 1) {
-      return true;
-    }
-    List<MemberNaming> mappings = memberNamings.get(0).getSecond();
-    if (mappings == null) {
-      return false;
-    }
-    return mappings.size() > 1;
-  }
+  boolean isAmbiguous();
 
-  @Override
-  public Stream<Element> stream() {
-    return memberNamings.stream()
-        .flatMap(
-            mappedNamePair -> {
-              RetraceClassResult.Element classElement = mappedNamePair.getFirst();
-              List<MemberNaming> memberNamings = mappedNamePair.getSecond();
-              if (memberNamings == null) {
-                return Stream.of(
-                    new RetraceFieldResult.Element(
-                        this,
-                        classElement,
-                        RetracedField.create(
-                            fieldDefinition.substituteHolder(
-                                classElement.getRetracedClass().getClassReference()))));
-              }
-              return memberNamings.stream()
-                  .map(
-                      memberNaming -> {
-                        FieldSignature fieldSignature =
-                            memberNaming.getOriginalSignature().asFieldSignature();
-                        RetracedClass holder =
-                            fieldSignature.isQualified()
-                                ? RetracedClass.create(
-                                    Reference.classFromDescriptor(
-                                        DescriptorUtils.javaTypeToDescriptor(
-                                            fieldSignature.toHolderFromQualified())))
-                                : classElement.getRetracedClass();
-                        return new Element(
-                            this,
-                            classElement,
-                            RetracedField.create(
-                                Reference.field(
-                                    holder.getClassReference(),
-                                    fieldSignature.isQualified()
-                                        ? fieldSignature.toUnqualifiedName()
-                                        : fieldSignature.name,
-                                    Reference.typeFromTypeName(fieldSignature.type))));
-                      });
-            });
-  }
+  @Keep
+  interface Element {
 
-  @Override
-  public RetraceFieldResult forEach(Consumer<Element> resultConsumer) {
-    stream().forEach(resultConsumer);
-    return this;
-  }
+    boolean isUnknown();
 
-  public static class Element {
+    RetracedField getField();
 
-    private final RetracedField fieldReference;
-    private final RetraceFieldResult retraceFieldResult;
-    private final RetraceClassResult.Element classElement;
+    RetraceFieldResult getRetraceFieldResult();
 
-    private Element(
-        RetraceFieldResult retraceFieldResult,
-        RetraceClassResult.Element classElement,
-        RetracedField fieldReference) {
-      this.classElement = classElement;
-      this.fieldReference = fieldReference;
-      this.retraceFieldResult = retraceFieldResult;
-    }
-
-    public boolean isUnknown() {
-      return fieldReference.isUnknown();
-    }
-
-    public RetracedField getField() {
-      return fieldReference;
-    }
-
-    public RetraceFieldResult getRetraceFieldResult() {
-      return retraceFieldResult;
-    }
-
-    public RetraceClassResult.Element getClassElement() {
-      return classElement;
-    }
+    RetraceClassResult.Element getClassElement();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java
index 95dc9d5..b439d94 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java
@@ -4,161 +4,34 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.RetraceUtils.methodReferenceFromMappedRange;
-
-import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.utils.Pair;
-import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collections;
+import com.android.tools.r8.Keep;
 import java.util.List;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
-public class RetraceFrameResult extends Result<RetraceFrameResult.Element, RetraceFrameResult> {
+@Keep
+public interface RetraceFrameResult {
 
-  private final RetraceClassResult classResult;
-  private final MethodDefinition methodDefinition;
-  private final int obfuscatedPosition;
-  private final List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges;
-  private final RetraceApi retracer;
+  Stream<Element> stream();
 
-  public RetraceFrameResult(
-      RetraceClassResult classResult,
-      List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges,
-      MethodDefinition methodDefinition,
-      int obfuscatedPosition,
-      RetraceApi retracer) {
-    this.classResult = classResult;
-    this.methodDefinition = methodDefinition;
-    this.obfuscatedPosition = obfuscatedPosition;
-    this.mappedRanges = mappedRanges;
-    this.retracer = retracer;
-  }
+  RetraceFrameResult forEach(Consumer<Element> resultConsumer);
 
-  @Override
-  public boolean isAmbiguous() {
-    return mappedRanges.size() > 1;
-  }
+  boolean isAmbiguous();
 
-  @Override
-  public Stream<Element> stream() {
-    return mappedRanges.stream()
-        .map(
-            mappedRangePair -> {
-              RetraceClassResult.Element classElement = mappedRangePair.getFirst();
-              List<MappedRange> mappedRanges = mappedRangePair.getSecond();
-              if (mappedRanges == null || mappedRanges.isEmpty()) {
-                return new Element(
-                    this,
-                    classElement,
-                    RetracedMethod.create(
-                        methodDefinition.substituteHolder(
-                            classElement.getRetracedClass().getClassReference())),
-                    ImmutableList.of(),
-                    obfuscatedPosition);
-              }
-              MappedRange mappedRange = mappedRanges.get(0);
-              MethodReference methodReference =
-                  methodReferenceFromMappedRange(
-                      mappedRange, classElement.getRetracedClass().getClassReference());
-              RetracedMethod retracedMethod =
-                  RetracedMethod.create(
-                      methodReference, mappedRange.getOriginalLineNumber(obfuscatedPosition));
-              return new Element(
-                  this, classElement, retracedMethod, mappedRanges, obfuscatedPosition);
-            });
-  }
+  @Keep
+  interface Element {
 
-  @Override
-  public RetraceFrameResult forEach(Consumer<Element> resultConsumer) {
-    stream().forEach(resultConsumer);
-    return this;
-  }
+    boolean isUnknown();
 
-  public static class Element implements RetraceClassMemberElement<RetracedMethod> {
+    RetracedMethod getTopFrame();
 
-    private final RetracedMethod methodReference;
-    private final RetraceFrameResult retraceFrameResult;
-    private final RetraceClassResult.Element classElement;
-    private final List<MappedRange> mappedRanges;
-    private final int obfuscatedPosition;
+    RetraceClassResult.Element getClassElement();
 
-    public Element(
-        RetraceFrameResult retraceFrameResult,
-        RetraceClassResult.Element classElement,
-        RetracedMethod methodReference,
-        List<MappedRange> mappedRanges,
-        int obfuscatedPosition) {
-      this.methodReference = methodReference;
-      this.retraceFrameResult = retraceFrameResult;
-      this.classElement = classElement;
-      this.mappedRanges = mappedRanges;
-      this.obfuscatedPosition = obfuscatedPosition;
-    }
+    void visitFrames(BiConsumer<RetracedMethod, Integer> consumer);
 
-    @Override
-    public boolean isUnknown() {
-      return methodReference.isUnknown();
-    }
+    RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile);
 
-    @Override
-    public boolean isFrameElement() {
-      return true;
-    }
-
-    @Override
-    public Element asFrameElement() {
-      return this;
-    }
-
-    @Override
-    public RetracedMethod getMember() {
-      return methodReference;
-    }
-
-    public RetracedMethod getTopFrame() {
-      return methodReference;
-    }
-
-    @Override
-    public RetraceClassResult.Element getClassElement() {
-      return classElement;
-    }
-
-    @Override
-    public void visitFrames(BiConsumer<RetracedMethod, Integer> consumer) {
-      int counter = 0;
-      consumer.accept(methodReference, counter++);
-      for (RetracedMethod outerFrame : getOuterFrames()) {
-        consumer.accept(outerFrame, counter++);
-      }
-    }
-
-    @Override
-    public RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile) {
-      return RetraceUtils.getSourceFile(
-          classElement, frame.getHolderClass(), sourceFile, retraceFrameResult.retracer);
-    }
-
-    public List<RetracedMethod> getOuterFrames() {
-      if (mappedRanges == null) {
-        return Collections.emptyList();
-      }
-      List<RetracedMethod> outerFrames = new ArrayList<>();
-      for (int i = 1; i < mappedRanges.size(); i++) {
-        MappedRange mappedRange = mappedRanges.get(i);
-        MethodReference methodReference =
-            methodReferenceFromMappedRange(
-                mappedRange, classElement.getRetracedClass().getClassReference());
-        outerFrames.add(
-            RetracedMethod.create(
-                MethodDefinition.create(methodReference),
-                mappedRange.getOriginalLineNumber(obfuscatedPosition)));
-      }
-      return outerFrames;
-    }
+    List<? extends RetracedMethod> getOuterFrames();
   }
 }
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 0906420..20c73e3 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -1,174 +1,35 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
-import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.utils.Pair;
-import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 @Keep
-public class RetraceMethodResult extends Result<RetraceMethodResult.Element, RetraceMethodResult> {
+public interface RetraceMethodResult {
 
-  private final MethodDefinition methodDefinition;
-  private final RetraceClassResult classResult;
-  private final List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges;
-  private final RetraceApi retracer;
+  RetraceFrameResult narrowByPosition(int position);
 
-  RetraceMethodResult(
-      RetraceClassResult classResult,
-      List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges,
-      MethodDefinition methodDefinition,
-      RetraceApi retracer) {
-    this.classResult = classResult;
-    this.mappedRanges = mappedRanges;
-    this.methodDefinition = methodDefinition;
-    this.retracer = retracer;
-    assert classResult != null;
-    assert !mappedRanges.isEmpty();
-  }
+  Stream<Element> stream();
 
-  @Override
-  public boolean isAmbiguous() {
-    if (mappedRanges.size() > 1) {
-      return true;
-    }
-    List<MappedRange> methodRanges = mappedRanges.get(0).getSecond();
-    if (methodRanges == null || methodRanges.isEmpty()) {
-      return false;
-    }
-    MappedRange lastRange = methodRanges.get(0);
-    for (MappedRange mappedRange : methodRanges) {
-      if (mappedRange != lastRange
-          && (mappedRange.minifiedRange == null
-              || !mappedRange.minifiedRange.equals(lastRange.minifiedRange))) {
-        return true;
-      }
-    }
-    return false;
-  }
+  RetraceMethodResult forEach(Consumer<Element> resultConsumer);
 
-  public RetraceFrameResult narrowByPosition(int position) {
-    List<Pair<RetraceClassResult.Element, List<MappedRange>>> narrowedRanges = new ArrayList<>();
-    List<Pair<RetraceClassResult.Element, List<MappedRange>>> noMappingRanges = new ArrayList<>();
-    for (Pair<RetraceClassResult.Element, List<MappedRange>> mappedRange : mappedRanges) {
-      if (mappedRange.getSecond() == null) {
-        noMappingRanges.add(new Pair<>(mappedRange.getFirst(), null));
-        continue;
-      }
-      List<MappedRange> ranges =
-          new MappedRangesOfName(mappedRange.getSecond()).allRangesForLine(position, false);
-      boolean hasAddedRanges = false;
-      if (!ranges.isEmpty()) {
-        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), ranges));
-        hasAddedRanges = true;
-      } else {
-        narrowedRanges = new ArrayList<>();
-        for (MappedRange mapped : mappedRange.getSecond()) {
-          if (mapped.minifiedRange == null) {
-            narrowedRanges.add(new Pair<>(mappedRange.getFirst(), ImmutableList.of(mapped)));
-            hasAddedRanges = true;
-          }
-        }
-      }
-      if (!hasAddedRanges) {
-        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), null));
-      }
-    }
-    return new RetraceFrameResult(
-        classResult,
-        narrowedRanges.isEmpty() ? noMappingRanges : narrowedRanges,
-        methodDefinition,
-        position,
-        retracer);
-  }
+  boolean isAmbiguous();
 
-  @Override
-  public Stream<Element> stream() {
-    return mappedRanges.stream()
-        .flatMap(
-            mappedRangePair -> {
-              RetraceClassResult.Element classElement = mappedRangePair.getFirst();
-              List<MappedRange> mappedRanges = mappedRangePair.getSecond();
-              if (mappedRanges == null || mappedRanges.isEmpty()) {
-                return Stream.of(
-                    new Element(
-                        this,
-                        classElement,
-                        RetracedMethod.create(
-                            methodDefinition.substituteHolder(
-                                classElement.getRetracedClass().getClassReference()))));
-              }
-              return mappedRanges.stream()
-                  .map(
-                      mappedRange -> {
-                        MethodReference methodReference =
-                            RetraceUtils.methodReferenceFromMappedRange(
-                                mappedRange, classElement.getRetracedClass().getClassReference());
-                        return new Element(
-                            this, classElement, RetracedMethod.create(methodReference));
-                      });
-            });
-  }
+  @Keep
+  interface Element {
 
-  @Override
-  public RetraceMethodResult forEach(Consumer<Element> resultConsumer) {
-    stream().forEach(resultConsumer);
-    return this;
-  }
+    boolean isUnknown();
 
-  public static class Element implements RetraceClassMemberElement<RetracedMethod> {
+    RetracedMethod getRetracedMethod();
 
-    private final RetracedMethod methodReference;
-    private final RetraceMethodResult retraceMethodResult;
-    private final RetraceClassResult.Element classElement;
+    RetraceMethodResult getRetraceMethodResult();
 
-    private Element(
-        RetraceMethodResult retraceMethodResult,
-        RetraceClassResult.Element classElement,
-        RetracedMethod methodReference) {
-      this.classElement = classElement;
-      this.retraceMethodResult = retraceMethodResult;
-      this.methodReference = methodReference;
-    }
+    RetraceClassResult.Element getClassElement();
 
-    @Override
-    public boolean isUnknown() {
-      return methodReference.isUnknown();
-    }
-
-    @Override
-    public RetracedMethod getMember() {
-      return methodReference;
-    }
-
-    public RetraceMethodResult getRetraceMethodResult() {
-      return retraceMethodResult;
-    }
-
-    @Override
-    public RetraceClassResult.Element getClassElement() {
-      return classElement;
-    }
-
-    @Override
-    public void visitFrames(BiConsumer<RetracedMethod, Integer> consumer) {
-      consumer.accept(methodReference, 0);
-    }
-
-    @Override
-    public RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile) {
-      return RetraceUtils.getSourceFile(
-          classElement, methodReference.getHolderClass(), sourceFile, retraceMethodResult.retracer);
-    }
+    RetraceSourceFileResult retraceSourceFile(String sourceFile);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
index e68e5c9..b2d4273 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
@@ -7,21 +7,9 @@
 import com.android.tools.r8.Keep;
 
 @Keep
-public class RetraceSourceFileResult {
+public interface RetraceSourceFileResult {
 
-  private final String filename;
-  private final boolean synthesized;
+  boolean isSynthesized();
 
-  RetraceSourceFileResult(String filename, boolean synthesized) {
-    this.filename = filename;
-    this.synthesized = synthesized;
-  }
-
-  public boolean isSynthesized() {
-    return synthesized;
-  }
-
-  public String getFilename() {
-    return filename;
-  }
+  String getFilename();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java
new file mode 100644
index 0000000..b69c237
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetraceStackTraceProxy<T extends StackTraceElementProxy<?>> {
+
+  boolean isAmbiguous();
+
+  boolean hasRetracedClass();
+
+  boolean hasRetracedMethod();
+
+  boolean hasSourceFile();
+
+  boolean hasLineNumber();
+
+  T getOriginalItem();
+
+  RetracedClass getRetracedClass();
+
+  RetracedMethod getRetracedMethod();
+
+  String getSourceFile();
+
+  int getLineNumber();
+}
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 d546d11..b9fa2d3 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
@@ -1,66 +1,26 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.retrace;
 
-import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.RetraceTypeResult.Element;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.RetracedTypeImpl;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
-public class RetraceTypeResult extends Result<Element, RetraceTypeResult> {
+@Keep
+public interface RetraceTypeResult {
 
-  private final TypeReference obfuscatedType;
-  private final RetraceApi retracer;
+  Stream<Element> stream();
 
-  private RetraceTypeResult(TypeReference obfuscatedType, RetraceApi retracer) {
-    this.obfuscatedType = obfuscatedType;
-    this.retracer = retracer;
-  }
+  RetraceTypeResult forEach(Consumer<Element> resultConsumer);
 
-  static RetraceTypeResult create(TypeReference obfuscatedType, RetraceApi retracer) {
-    return new RetraceTypeResult(obfuscatedType, retracer);
-  }
+  boolean isAmbiguous();
 
-  @Override
-  public Stream<Element> stream() {
-    // Handle void and primitive types as single element results.
-    if (obfuscatedType == null || obfuscatedType.isPrimitive()) {
-      return Stream.of(new Element(RetracedType.create(obfuscatedType)));
-    }
-    if (obfuscatedType.isArray()) {
-      int dimensions = obfuscatedType.asArray().getDimensions();
-      return retracer.retrace(obfuscatedType.asArray().getBaseType()).stream()
-          .map(
-              baseElement ->
-                  new Element(RetracedType.create(baseElement.retracedType.toArray(dimensions))));
-    }
-    return retracer.retrace(obfuscatedType.asClass()).stream()
-        .map(classElement -> new Element(classElement.getRetracedClass().getRetracedType()));
-  }
+  @Keep
+  interface Element {
 
-  @Override
-  public boolean isAmbiguous() {
-    return false;
-  }
-
-  @Override
-  public RetraceTypeResult forEach(Consumer<Element> resultConsumer) {
-    stream().forEach(resultConsumer);
-    return this;
-  }
-
-  public static class Element {
-
-    private final RetracedType retracedType;
-
-    public Element(RetracedType retracedType) {
-      this.retracedType = retracedType;
-    }
-
-    public RetracedType getType() {
-      return retracedType;
-    }
+    RetracedTypeImpl getType();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedClass.java b/src/main/java/com/android/tools/r8/retrace/RetracedClass.java
index af064fc..e66931e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedClass.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedClass.java
@@ -6,50 +6,16 @@
 
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.retrace.internal.RetracedTypeImpl;
 
 @Keep
-public final class RetracedClass {
+public interface RetracedClass {
 
-  private final ClassReference classReference;
+  String getTypeName();
 
-  private RetracedClass(ClassReference classReference) {
-    assert classReference != null;
-    this.classReference = classReference;
-  }
+  String getBinaryName();
 
-  public static RetracedClass create(ClassReference classReference) {
-    return new RetracedClass(classReference);
-  }
+  RetracedTypeImpl getRetracedType();
 
-  public String getTypeName() {
-    return classReference.getTypeName();
-  }
-
-  public String getBinaryName() {
-    return classReference.getBinaryName();
-  }
-
-  public RetracedType getRetracedType() {
-    return RetracedType.create(classReference);
-  }
-
-  ClassReference getClassReference() {
-    return classReference;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    return classReference.equals(((RetracedClass) o).classReference);
-  }
-
-  @Override
-  public int hashCode() {
-    return classReference.hashCode();
-  }
+  ClassReference getClassReference();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java b/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java
index e05be47..10aa77c 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.retrace;
 
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.RetracedClassImpl;
+
+@Keep
 public interface RetracedClassMember {
 
-  RetracedClass getHolderClass();
+  RetracedClassImpl getHolderClass();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedField.java b/src/main/java/com/android/tools/r8/retrace/RetracedField.java
index 132d0c7..2af8c71 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedField.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedField.java
@@ -7,122 +7,23 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.TypeReference;
-import java.util.Objects;
 
 @Keep
-public abstract class RetracedField implements RetracedClassMember {
+public interface RetracedField extends RetracedClassMember {
 
-  private RetracedField() {}
+  boolean isUnknown();
 
-  public boolean isUnknown() {
-    return true;
-  }
+  boolean isKnown();
 
-  public final boolean isKnown() {
-    return !isUnknown();
-  }
+  KnownRetracedField asKnown();
 
-  public KnownRetracedField asKnown() {
-    return null;
-  }
+  String getFieldName();
 
-  public abstract String getFieldName();
+  @Keep
+  interface KnownRetracedField extends RetracedField {
 
-  public static final class KnownRetracedField extends RetracedField {
+    TypeReference getFieldType();
 
-    private final FieldReference fieldReference;
-
-    private KnownRetracedField(FieldReference fieldReference) {
-      this.fieldReference = fieldReference;
-    }
-
-    @Override
-    public boolean isUnknown() {
-      return false;
-    }
-
-    @Override
-    public KnownRetracedField asKnown() {
-      return this;
-    }
-
-    @Override
-    public RetracedClass getHolderClass() {
-      return RetracedClass.create(fieldReference.getHolderClass());
-    }
-
-    @Override
-    public String getFieldName() {
-      return fieldReference.getFieldName();
-    }
-
-    public TypeReference getFieldType() {
-      return fieldReference.getFieldType();
-    }
-
-    public FieldReference getFieldReference() {
-      return fieldReference;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      KnownRetracedField that = (KnownRetracedField) o;
-      return fieldReference.equals(that.fieldReference);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(fieldReference);
-    }
-  }
-
-  public static final class UnknownRetracedField extends RetracedField {
-
-    private final FieldDefinition fieldDefinition;
-
-    private UnknownRetracedField(FieldDefinition fieldDefinition) {
-      this.fieldDefinition = fieldDefinition;
-    }
-
-    @Override
-    public RetracedClass getHolderClass() {
-      return RetracedClass.create(fieldDefinition.getHolderClass());
-    }
-
-    @Override
-    public String getFieldName() {
-      return fieldDefinition.getName();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      UnknownRetracedField that = (UnknownRetracedField) o;
-      return fieldDefinition.equals(that.fieldDefinition);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(fieldDefinition);
-    }
-  }
-
-  static RetracedField create(FieldReference fieldReference) {
-    return new KnownRetracedField(fieldReference);
-  }
-
-  static RetracedField create(FieldDefinition fieldDefinition) {
-    return new UnknownRetracedField(fieldDefinition);
+    FieldReference getFieldReference();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
index 937ded1..bfc95e9 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
@@ -8,165 +8,31 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
 import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
 
 @Keep
-public abstract class RetracedMethod implements RetracedClassMember {
+public interface RetracedMethod extends RetracedClassMember {
 
-  private static final int NO_POSITION = -1;
+  boolean isUnknown();
 
-  private RetracedMethod() {}
+  boolean isKnown();
 
-  public boolean isUnknown() {
-    return true;
-  }
+  KnownRetracedMethod asKnown();
 
-  public final boolean isKnown() {
-    return !isUnknown();
-  }
+  String getMethodName();
 
-  public KnownRetracedMethod asKnown() {
-    return null;
-  }
+  boolean hasPosition();
 
-  public abstract String getMethodName();
+  int getOriginalPositionOrDefault(int defaultPosition);
 
-  public abstract boolean hasPosition();
+  @Keep
+  interface KnownRetracedMethod extends RetracedMethod {
 
-  public abstract int getOriginalPositionOrDefault(int defaultPosition);
+    boolean isVoid();
 
-  public static final class KnownRetracedMethod extends RetracedMethod {
+    TypeReference getReturnType();
 
-    private final MethodReference methodReference;
-    private final int position;
+    List<TypeReference> getFormalTypes();
 
-    private KnownRetracedMethod(MethodReference methodReference, int position) {
-      assert methodReference != null;
-      this.methodReference = methodReference;
-      this.position = position;
-    }
-
-    @Override
-    public boolean isUnknown() {
-      return false;
-    }
-
-    public boolean isVoid() {
-      return methodReference.getReturnType() == null;
-    }
-
-    @Override
-    public KnownRetracedMethod asKnown() {
-      return this;
-    }
-
-    @Override
-    public RetracedClass getHolderClass() {
-      return RetracedClass.create(methodReference.getHolderClass());
-    }
-
-    @Override
-    public String getMethodName() {
-      return methodReference.getMethodName();
-    }
-
-    @Override
-    public boolean hasPosition() {
-      return position != NO_POSITION;
-    }
-
-    @Override
-    public int getOriginalPositionOrDefault(int defaultPosition) {
-      return hasPosition() ? position : defaultPosition;
-    }
-
-    public TypeReference getReturnType() {
-      assert !isVoid();
-      return methodReference.getReturnType();
-    }
-
-    public List<TypeReference> getFormalTypes() {
-      return methodReference.getFormalTypes();
-    }
-
-    public MethodReference getMethodReference() {
-      return methodReference;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      KnownRetracedMethod that = (KnownRetracedMethod) o;
-      return position == that.position && methodReference.equals(that.methodReference);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(methodReference, position);
-    }
-  }
-
-  public static final class UnknownRetracedMethod extends RetracedMethod {
-
-    private final MethodDefinition methodDefinition;
-    private final int position;
-
-    private UnknownRetracedMethod(MethodDefinition methodDefinition, int position) {
-      this.methodDefinition = methodDefinition;
-      this.position = position;
-    }
-
-    @Override
-    public RetracedClass getHolderClass() {
-      return RetracedClass.create(methodDefinition.getHolderClass());
-    }
-
-    @Override
-    public String getMethodName() {
-      return methodDefinition.getName();
-    }
-
-    @Override
-    public boolean hasPosition() {
-      return position != NO_POSITION;
-    }
-
-    @Override
-    public int getOriginalPositionOrDefault(int defaultPosition) {
-      return hasPosition() ? position : defaultPosition;
-    }
-
-    public Optional<MethodReference> getMethodReference() {
-      if (!methodDefinition.isFullMethodDefinition()) {
-        return Optional.empty();
-      }
-      return Optional.of(methodDefinition.asFullMethodDefinition().getMethodReference());
-    }
-  }
-
-  static RetracedMethod create(MethodDefinition methodDefinition) {
-    return create(methodDefinition, NO_POSITION);
-  }
-
-  static RetracedMethod create(MethodDefinition methodDefinition, int position) {
-    if (methodDefinition.isFullMethodDefinition()) {
-      return new KnownRetracedMethod(
-          methodDefinition.asFullMethodDefinition().getMethodReference(), position);
-    }
-    return new UnknownRetracedMethod(methodDefinition, position);
-  }
-
-  static RetracedMethod create(MethodReference methodReference) {
-    return create(methodReference, NO_POSITION);
-  }
-
-  static RetracedMethod create(MethodReference methodReference, int position) {
-    return new KnownRetracedMethod(methodReference, position);
+    MethodReference getMethodReference();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedType.java b/src/main/java/com/android/tools/r8/retrace/RetracedType.java
index 016c8df..7d5baa1 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedType.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedType.java
@@ -5,53 +5,16 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
-import java.util.Objects;
 
 @Keep
-public final class RetracedType {
+public interface RetracedType {
 
-  private final TypeReference typeReference;
+  boolean isVoid();
 
-  private RetracedType(TypeReference typeReference) {
-    this.typeReference = typeReference;
-  }
+  TypeReference toArray(int dimensions);
 
-  static RetracedType create(TypeReference typeReference) {
-    return new RetracedType(typeReference);
-  }
+  String getTypeName();
 
-  public boolean isVoid() {
-    return typeReference == null;
-  }
-
-  public TypeReference toArray(int dimensions) {
-    return Reference.array(typeReference, dimensions);
-  }
-
-  public String getTypeName() {
-    assert !isVoid();
-    return typeReference.getTypeName();
-  }
-
-  public TypeReference getTypeReference() {
-    return typeReference;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    return typeReference.equals(((RetracedType) o).typeReference);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(typeReference);
-  }
+  TypeReference getTypeReference();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index 279253c..57b7607 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -4,66 +4,23 @@
 
 package com.android.tools.r8.retrace;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
 
-/** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
+/** This is the main api interface for retrace. */
 @Keep
-public class Retracer implements RetraceApi {
+public interface Retracer {
 
-  private final ClassNameMapper classNameMapper;
+  RetraceClassResult retraceClass(ClassReference classReference);
 
-  private Retracer(ClassNameMapper classNameMapper) {
-    this.classNameMapper = classNameMapper;
-    assert classNameMapper != null;
-  }
+  RetraceMethodResult retraceMethod(MethodReference methodReference);
 
-  public static RetraceApi create(
-      ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
-    if (proguardMapProducer instanceof DirectClassNameMapperProguardMapProducer) {
-      return new Retracer(
-          ((DirectClassNameMapperProguardMapProducer) proguardMapProducer).getClassNameMapper());
-    }
-    try {
-      ClassNameMapper classNameMapper =
-          ClassNameMapper.mapperFromString(proguardMapProducer.get(), diagnosticsHandler);
-      return new Retracer(classNameMapper);
-    } catch (Throwable throwable) {
-      throw new InvalidMappingFileException(throwable);
-    }
-  }
+  RetraceFrameResult retraceFrame(MethodReference methodReference, int position);
 
-  @Override
-  public RetraceMethodResult retrace(MethodReference methodReference) {
-    return retrace(methodReference.getHolderClass()).lookupMethod(methodReference.getMethodName());
-  }
+  RetraceFieldResult retraceField(FieldReference fieldReference);
 
-  @Override
-  public RetraceFieldResult retrace(FieldReference fieldReference) {
-    return retrace(fieldReference.getHolderClass()).lookupField(fieldReference.getFieldName());
-  }
-
-  @Override
-  public RetraceFrameResult retrace(MethodReference methodReference, int position) {
-    return retrace(methodReference.getHolderClass())
-        .lookupMethod(methodReference.getMethodName())
-        .narrowByPosition(position);
-  }
-
-  @Override
-  public RetraceClassResult retrace(ClassReference classReference) {
-    return RetraceClassResult.create(
-        classReference, classNameMapper.getClassNaming(classReference.getTypeName()), this);
-  }
-
-  @Override
-  public RetraceTypeResult retrace(TypeReference typeReference) {
-    return RetraceTypeResult.create(typeReference, this);
-  }
+  RetraceTypeResult retraceType(TypeReference typeReference);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
index 042b1f8..17d9005 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
@@ -5,191 +5,11 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.references.Reference;
-import java.util.ArrayList;
-import java.util.List;
+import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl.RetraceStackTraceProxyImpl;
 import java.util.stream.Stream;
 
 @Keep
-public class StackTraceElementProxyRetracer<T extends StackTraceElementProxy<?>> {
+public interface StackTraceElementProxyRetracer<T extends StackTraceElementProxy<?>> {
 
-  private final RetraceApi retracer;
-
-  public StackTraceElementProxyRetracer(RetraceApi retracer) {
-    this.retracer = retracer;
-  }
-
-  public Stream<RetraceStackTraceProxy<T>> retrace(T element) {
-    if (!element.hasClassName()) {
-      return Stream.of(RetraceStackTraceProxy.builder(element).build());
-    }
-    RetraceClassResult classResult =
-        retracer.retrace(Reference.classFromTypeName(element.className()));
-    List<RetraceStackTraceProxy<T>> retracedProxies = new ArrayList<>();
-    if (!element.hasMethodName()) {
-      classResult.forEach(
-          classElement -> {
-            RetraceStackTraceProxy.Builder<T> proxy =
-                RetraceStackTraceProxy.builder(element)
-                    .setRetracedClassElement(classElement.getRetracedClass())
-                    .setAmbiguous(classResult.isAmbiguous());
-            if (element.hasFileName()) {
-              proxy.setSourceFile(classElement.retraceSourceFile(element.fileName()).getFilename());
-            }
-            retracedProxies.add(proxy.build());
-          });
-    } else {
-      RetraceMethodResult retraceMethodResult = classResult.lookupMethod(element.methodName());
-      Result<? extends RetraceClassMemberElement<RetracedMethod>, ?> methodResult;
-      if (element.hasLineNumber()) {
-        methodResult = retraceMethodResult.narrowByPosition(element.lineNumber());
-      } else {
-        methodResult = retraceMethodResult;
-      }
-      methodResult.forEach(
-          methodElement -> {
-            methodElement.visitFrames(
-                (frame, index) -> {
-                  RetraceStackTraceProxy.Builder<T> proxy =
-                      RetraceStackTraceProxy.builder(element)
-                          .setRetracedClassElement(frame.getHolderClass())
-                          .setRetracedMethodElement(frame)
-                          .setAmbiguous(methodResult.isAmbiguous() && index == 0);
-                  if (element.hasLineNumber()) {
-                    proxy.setLineNumber(frame.getOriginalPositionOrDefault(element.lineNumber()));
-                  }
-                  if (element.hasFileName()) {
-                    proxy.setSourceFile(
-                        methodElement.retraceSourceFile(frame, element.fileName()).getFilename());
-                  }
-                  retracedProxies.add(proxy.build());
-                });
-          });
-    }
-    return retracedProxies.stream();
-  }
-
-  @Keep
-  public static class RetraceStackTraceProxy<T extends StackTraceElementProxy<?>> {
-
-    private final T originalItem;
-    private final RetracedClass retracedClass;
-    private final RetracedMethod retracedMethod;
-    private final String sourceFile;
-    private final int lineNumber;
-    private final boolean isAmbiguous;
-
-    private RetraceStackTraceProxy(
-        T originalItem,
-        RetracedClass retracedClass,
-        RetracedMethod retracedMethod,
-        String sourceFile,
-        int lineNumber,
-        boolean isAmbiguous) {
-      assert originalItem != null;
-      this.originalItem = originalItem;
-      this.retracedClass = retracedClass;
-      this.retracedMethod = retracedMethod;
-      this.sourceFile = sourceFile;
-      this.lineNumber = lineNumber;
-      this.isAmbiguous = isAmbiguous;
-    }
-
-    public boolean isAmbiguous() {
-      return isAmbiguous;
-    }
-
-    public boolean hasRetracedClass() {
-      return retracedClass != null;
-    }
-
-    public boolean hasRetracedMethod() {
-      return retracedMethod != null;
-    }
-
-    public boolean hasSourceFile() {
-      return sourceFile != null;
-    }
-
-    public boolean hasLineNumber() {
-      return lineNumber != -1;
-    }
-
-    public T getOriginalItem() {
-      return originalItem;
-    }
-
-    public RetracedClass getRetracedClass() {
-      return retracedClass;
-    }
-
-    public RetracedMethod getRetracedMethod() {
-      return retracedMethod;
-    }
-
-    public String getSourceFile() {
-      return sourceFile;
-    }
-
-    private static <T extends StackTraceElementProxy<?>> Builder<T> builder(T originalElement) {
-      return new Builder<>(originalElement);
-    }
-
-    public int getLineNumber() {
-      return lineNumber;
-    }
-
-    private static class Builder<T extends StackTraceElementProxy<?>> {
-
-      private final T originalElement;
-      private RetracedClass classContext;
-      private RetracedMethod methodContext;
-      private String sourceFile;
-      private int lineNumber = -1;
-      private boolean isAmbiguous;
-
-      private Builder(T originalElement) {
-        this.originalElement = originalElement;
-      }
-
-      private Builder<T> setRetracedClassElement(RetracedClass retracedClass) {
-        this.classContext = retracedClass;
-        return this;
-      }
-
-      private Builder<T> setRetracedMethodElement(RetracedMethod methodElement) {
-        this.methodContext = methodElement;
-        return this;
-      }
-
-      private Builder<T> setSourceFile(String sourceFile) {
-        this.sourceFile = sourceFile;
-        return this;
-      }
-
-      private Builder<T> setLineNumber(int lineNumber) {
-        this.lineNumber = lineNumber;
-        return this;
-      }
-
-      private Builder<T> setAmbiguous(boolean ambiguous) {
-        this.isAmbiguous = ambiguous;
-        return this;
-      }
-
-      private RetraceStackTraceProxy<T> build() {
-        RetracedClass retracedClass = classContext;
-        if (methodContext != null) {
-          retracedClass = methodContext.getHolderClass();
-        }
-        return new RetraceStackTraceProxy<>(
-            originalElement,
-            retracedClass,
-            methodContext,
-            sourceFile != null ? sourceFile : null,
-            lineNumber,
-            isAmbiguous);
-      }
-    }
-  }
+  Stream<RetraceStackTraceProxyImpl<T>> retrace(T element);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/AmbiguousComparator.java b/src/main/java/com/android/tools/r8/retrace/internal/AmbiguousComparator.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/retrace/AmbiguousComparator.java
rename to src/main/java/com/android/tools/r8/retrace/internal/AmbiguousComparator.java
index 35266e7..d3c5a01 100644
--- a/src/main/java/com/android/tools/r8/retrace/AmbiguousComparator.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/AmbiguousComparator.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.retrace;
+package com.android.tools.r8.retrace.internal;
 
 import java.util.Comparator;
 import java.util.function.BiFunction;
diff --git a/src/main/java/com/android/tools/r8/retrace/Definition.java b/src/main/java/com/android/tools/r8/retrace/internal/Definition.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/retrace/Definition.java
rename to src/main/java/com/android/tools/r8/retrace/internal/Definition.java
index f955ac0..65a1736 100644
--- a/src/main/java/com/android/tools/r8/retrace/Definition.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/Definition.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.retrace;
+package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.references.ClassReference;
 
diff --git a/src/main/java/com/android/tools/r8/retrace/DirectClassNameMapperProguardMapProducer.java b/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/retrace/DirectClassNameMapperProguardMapProducer.java
rename to src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
index 8efe05a..07bf237 100644
--- a/src/main/java/com/android/tools/r8/retrace/DirectClassNameMapperProguardMapProducer.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.retrace;
+package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
diff --git a/src/main/java/com/android/tools/r8/retrace/FieldDefinition.java b/src/main/java/com/android/tools/r8/retrace/internal/FieldDefinition.java
similarity index 98%
rename from src/main/java/com/android/tools/r8/retrace/FieldDefinition.java
rename to src/main/java/com/android/tools/r8/retrace/internal/FieldDefinition.java
index 3dcf1c9..4a3b93c 100644
--- a/src/main/java/com/android/tools/r8/retrace/FieldDefinition.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/FieldDefinition.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.retrace;
+package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
diff --git a/src/main/java/com/android/tools/r8/retrace/MethodDefinition.java b/src/main/java/com/android/tools/r8/retrace/internal/MethodDefinition.java
similarity index 98%
rename from src/main/java/com/android/tools/r8/retrace/MethodDefinition.java
rename to src/main/java/com/android/tools/r8/retrace/internal/MethodDefinition.java
index 8641f13..b1e23f5 100644
--- a/src/main/java/com/android/tools/r8/retrace/MethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MethodDefinition.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.retrace;
+package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
diff --git a/src/main/java/com/android/tools/r8/retrace/PlainStackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/retrace/PlainStackTraceVisitor.java
rename to src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
index 1234b4d..0aac05f 100644
--- a/src/main/java/com/android/tools/r8/retrace/PlainStackTraceVisitor.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
@@ -2,12 +2,12 @@
 // 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;
+package com.android.tools.r8.retrace.internal;
 
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.retrace.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder;
+import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.List;
 import java.util.function.Consumer;
@@ -19,7 +19,7 @@
   private final List<String> stackTrace;
   private final DiagnosticsHandler diagnosticsHandler;
 
-  PlainStackTraceVisitor(List<String> stackTrace, DiagnosticsHandler diagnosticsHandler) {
+  public PlainStackTraceVisitor(List<String> stackTrace, DiagnosticsHandler diagnosticsHandler) {
     this.stackTrace = stackTrace;
     this.diagnosticsHandler = diagnosticsHandler;
   }
@@ -209,7 +209,7 @@
   private StackTraceElementStringProxy parseLine(int lineNumber, String line) {
     if (line == null) {
       diagnosticsHandler.error(RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
-      throw new Retrace.RetraceAbortException();
+      throw new RetraceAbortException();
     }
     // Most lines are 'at lines' so attempt to parse it first.
     StackTraceElementStringProxy parsedLine = AtLine.tryParse(line);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceAbortException.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceAbortException.java
new file mode 100644
index 0000000..60eee2f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceAbortException.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+public class RetraceAbortException extends RuntimeException {}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
new file mode 100644
index 0000000..45b7fdd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -0,0 +1,346 @@
+// 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.internal;
+
+import static com.android.tools.r8.naming.MemberNaming.NoSignature.NO_SIGNATURE;
+import static com.android.tools.r8.retrace.internal.RetraceUtils.synthesizeFileName;
+
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
+import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetraceClassResult;
+import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+public class RetraceClassResultImpl implements RetraceClassResult {
+
+  private final ClassReference obfuscatedReference;
+  private final ClassNamingForNameMapper mapper;
+  private final RetracerImpl retracer;
+
+  private RetraceClassResultImpl(
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetracerImpl retracer) {
+    this.obfuscatedReference = obfuscatedReference;
+    this.mapper = mapper;
+    this.retracer = retracer;
+  }
+
+  static RetraceClassResultImpl create(
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetracerImpl retracer) {
+    return new RetraceClassResultImpl(obfuscatedReference, mapper, retracer);
+  }
+
+  @Override
+  public RetraceFieldResultImpl lookupField(String fieldName) {
+    return lookupField(FieldDefinition.create(obfuscatedReference, fieldName));
+  }
+
+  @Override
+  public RetraceFieldResultImpl lookupField(String fieldName, TypeReference fieldType) {
+    return lookupField(
+        FieldDefinition.create(Reference.field(obfuscatedReference, fieldName, fieldType)));
+  }
+
+  @Override
+  public RetraceMethodResultImpl lookupMethod(String methodName) {
+    return lookupMethod(MethodDefinition.create(obfuscatedReference, methodName));
+  }
+
+  @Override
+  public RetraceMethodResultImpl lookupMethod(
+      String methodName, List<TypeReference> formalTypes, TypeReference returnType) {
+    return lookupMethod(
+        MethodDefinition.create(
+            Reference.method(obfuscatedReference, methodName, formalTypes, returnType)));
+  }
+
+  private RetraceFieldResultImpl lookupField(FieldDefinition fieldDefinition) {
+    return lookup(
+        fieldDefinition,
+        (mapper, name) -> {
+          List<MemberNaming> memberNamings = mapper.mappedFieldNamingsByName.get(name);
+          if (memberNamings == null || memberNamings.isEmpty()) {
+            return null;
+          }
+          return memberNamings;
+        },
+        RetraceFieldResultImpl::new);
+  }
+
+  private RetraceMethodResultImpl lookupMethod(MethodDefinition methodDefinition) {
+    return lookup(
+        methodDefinition,
+        (mapper, name) -> {
+          MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(name);
+          if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
+            return null;
+          }
+          return mappedRanges.getMappedRanges();
+        },
+        RetraceMethodResultImpl::new);
+  }
+
+  private <T, R, D extends Definition> R lookup(
+      D definition,
+      BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
+      ResultConstructor<T, R, D> constructor) {
+    List<Pair<ElementImpl, T>> mappings = new ArrayList<>();
+    internalStream()
+        .forEach(
+            element -> {
+              if (mapper != null) {
+                assert element.mapper != null;
+                T mappedElements = lookupFunction.apply(element.mapper, definition.getName());
+                if (mappedElements != null) {
+                  mappings.add(new Pair<>(element, mappedElements));
+                  return;
+                }
+              }
+              mappings.add(new Pair<>(element, null));
+            });
+    return constructor.create(this, mappings, definition, retracer);
+  }
+
+  @Override
+  public RetraceFrameResultImpl lookupFrame(String methodName) {
+    return lookupFrame(MethodDefinition.create(obfuscatedReference, methodName), -1);
+  }
+
+  @Override
+  public RetraceFrameResultImpl lookupFrame(String methodName, int position) {
+    return lookupFrame(MethodDefinition.create(obfuscatedReference, methodName), position);
+  }
+
+  @Override
+  public RetraceFrameResultImpl lookupFrame(
+      String methodName, int position, List<TypeReference> formalTypes, TypeReference returnType) {
+    return lookupFrame(
+        MethodDefinition.create(
+            Reference.method(obfuscatedReference, methodName, formalTypes, returnType)),
+        position);
+  }
+
+  private RetraceFrameResultImpl lookupFrame(MethodDefinition definition, int position) {
+    List<Pair<ElementImpl, List<MappedRange>>> mappings = new ArrayList<>();
+    internalStream()
+        .forEach(
+            element ->
+                mappings.add(
+                    new Pair<>(element, getMappedRangesForFrame(element, definition, position))));
+    return new RetraceFrameResultImpl(this, mappings, definition, position, retracer);
+  }
+
+  private List<MappedRange> getMappedRangesForFrame(
+      ElementImpl element, MethodDefinition definition, int position) {
+    if (mapper == null) {
+      return null;
+    }
+    assert element.mapper != null;
+    MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(definition.getName());
+    if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
+      return null;
+    }
+    if (position <= 0) {
+      return mappedRanges.getMappedRanges();
+    }
+    List<MappedRange> mappedRangesForPosition = mappedRanges.allRangesForLine(position, false);
+    return mappedRangesForPosition.isEmpty()
+        ? mappedRanges.getMappedRanges()
+        : mappedRangesForPosition;
+  }
+
+  boolean hasRetraceResult() {
+    return mapper != null;
+  }
+
+  @Override
+  public Stream<Element> stream() {
+    return Stream.of(createElement());
+  }
+
+  private Stream<ElementImpl> internalStream() {
+    return Stream.of(createElement());
+  }
+
+  private ElementImpl createElement() {
+    return new ElementImpl(
+        this,
+        RetracedClassImpl.create(
+            mapper == null
+                ? obfuscatedReference
+                : Reference.classFromTypeName(mapper.originalName)),
+        mapper);
+  }
+
+  @Override
+  public RetraceClassResultImpl forEach(Consumer<Element> resultConsumer) {
+    stream().forEach(resultConsumer);
+    return this;
+  }
+
+  private interface ResultConstructor<T, R, D> {
+    R create(
+        RetraceClassResultImpl classResult,
+        List<Pair<ElementImpl, T>> mappings,
+        D definition,
+        RetracerImpl retracer);
+  }
+
+  @Override
+  public boolean isAmbiguous() {
+    // Currently we have no way of producing ambiguous class results.
+    return false;
+  }
+
+  public static class ElementImpl implements Element {
+
+    private final RetraceClassResultImpl classResult;
+    private final RetracedClassImpl classReference;
+    private final ClassNamingForNameMapper mapper;
+
+    public ElementImpl(
+        RetraceClassResultImpl classResult,
+        RetracedClassImpl classReference,
+        ClassNamingForNameMapper mapper) {
+      this.classResult = classResult;
+      this.classReference = classReference;
+      this.mapper = mapper;
+    }
+
+    @Override
+    public RetracedClassImpl getRetracedClass() {
+      return classReference;
+    }
+
+    @Override
+    public RetraceClassResultImpl getRetraceClassResult() {
+      return classResult;
+    }
+
+    @Override
+    public RetraceSourceFileResultImpl retraceSourceFile(String sourceFile) {
+      if (mapper != null && mapper.getAdditionalMappings().size() > 0) {
+        List<MappingInformation> mappingInformations =
+            mapper.getAdditionalMappings().get(NO_SIGNATURE);
+        if (mappingInformations != null) {
+          for (MappingInformation mappingInformation : mappingInformations) {
+            if (mappingInformation.isFileNameInformation()) {
+              return new RetraceSourceFileResultImpl(
+                  mappingInformation.asFileNameInformation().getFileName(), false);
+            }
+          }
+        }
+      }
+      return new RetraceSourceFileResultImpl(
+          synthesizeFileName(
+              classReference.getTypeName(),
+              classResult.obfuscatedReference.getTypeName(),
+              sourceFile,
+              mapper != null),
+          true);
+    }
+
+    @Override
+    public RetraceFieldResultImpl lookupField(String fieldName) {
+      return lookupField(FieldDefinition.create(classReference.getClassReference(), fieldName));
+    }
+
+    private RetraceFieldResultImpl lookupField(FieldDefinition fieldDefinition) {
+      return lookup(
+          fieldDefinition,
+          (mapper, name) -> {
+            List<MemberNaming> memberNamings = mapper.mappedFieldNamingsByName.get(name);
+            if (memberNamings == null || memberNamings.isEmpty()) {
+              return null;
+            }
+            return memberNamings;
+          },
+          RetraceFieldResultImpl::new);
+    }
+
+    @Override
+    public RetraceMethodResultImpl lookupMethod(String methodName) {
+      return lookupMethod(MethodDefinition.create(classReference.getClassReference(), methodName));
+    }
+
+    private RetraceMethodResultImpl lookupMethod(MethodDefinition methodDefinition) {
+      return lookup(
+          methodDefinition,
+          (mapper, name) -> {
+            MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(name);
+            if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
+              return null;
+            }
+            return mappedRanges.getMappedRanges();
+          },
+          RetraceMethodResultImpl::new);
+    }
+
+    private <T, R, D extends Definition> R lookup(
+        D definition,
+        BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
+        ResultConstructor<T, R, D> constructor) {
+      List<Pair<ElementImpl, T>> mappings = ImmutableList.of();
+      if (mapper != null) {
+        T result = lookupFunction.apply(mapper, definition.getName());
+        if (result != null) {
+          mappings = ImmutableList.of(new Pair<>(this, result));
+        }
+      }
+      if (mappings.isEmpty()) {
+        mappings = ImmutableList.of(new Pair<>(this, null));
+      }
+      return constructor.create(classResult, mappings, definition, classResult.retracer);
+    }
+
+    @Override
+    public RetraceFrameResultImpl lookupFrame(String methodName) {
+      return lookupFrame(methodName, -1);
+    }
+
+    @Override
+    public RetraceFrameResultImpl lookupFrame(String methodName, int position) {
+      return lookupFrame(
+          MethodDefinition.create(classReference.getClassReference(), methodName), position);
+    }
+
+    @Override
+    public RetraceFrameResult lookupFrame(
+        String methodName,
+        int position,
+        List<TypeReference> formalTypes,
+        TypeReference returnType) {
+      return lookupFrame(
+          MethodDefinition.create(
+              Reference.method(
+                  classReference.getClassReference(), methodName, formalTypes, returnType)),
+          position);
+    }
+
+    private RetraceFrameResultImpl lookupFrame(MethodDefinition definition, int position) {
+      MethodDefinition methodDefinition =
+          MethodDefinition.create(classReference.getClassReference(), definition.getName());
+      return new RetraceFrameResultImpl(
+          classResult,
+          ImmutableList.of(
+              new Pair<>(
+                  this, classResult.getMappedRangesForFrame(this, methodDefinition, position))),
+          methodDefinition,
+          position,
+          classResult.retracer);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommandLineResult.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceCommandLineResult.java
similarity index 67%
rename from src/main/java/com/android/tools/r8/retrace/RetraceCommandLineResult.java
rename to src/main/java/com/android/tools/r8/retrace/internal/RetraceCommandLineResult.java
index 00f1857..8686ede 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommandLineResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceCommandLineResult.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.retrace;
+package com.android.tools.r8.retrace.internal;
 
 import java.util.List;
 
@@ -10,7 +10,7 @@
 
   private final List<String> nodes;
 
-  RetraceCommandLineResult(List<String> nodes) {
+  public RetraceCommandLineResult(List<String> nodes) {
     this.nodes = nodes;
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
new file mode 100644
index 0000000..066b979
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
@@ -0,0 +1,135 @@
+// 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.internal;
+
+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.Reference;
+import com.android.tools.r8.retrace.RetraceFieldResult;
+import com.android.tools.r8.retrace.Retracer;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Pair;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+@Keep
+public class RetraceFieldResultImpl implements RetraceFieldResult {
+
+  private final RetraceClassResultImpl classResult;
+  private final List<Pair<RetraceClassResultImpl.ElementImpl, List<MemberNaming>>> memberNamings;
+  private final FieldDefinition fieldDefinition;
+  private final Retracer retracer;
+
+  RetraceFieldResultImpl(
+      RetraceClassResultImpl classResult,
+      List<Pair<RetraceClassResultImpl.ElementImpl, List<MemberNaming>>> memberNamings,
+      FieldDefinition fieldDefinition,
+      Retracer retracer) {
+    this.classResult = classResult;
+    this.memberNamings = memberNamings;
+    this.fieldDefinition = fieldDefinition;
+    this.retracer = retracer;
+    assert classResult != null;
+    assert !memberNamings.isEmpty();
+  }
+
+  @Override
+  public Stream<Element> stream() {
+    return memberNamings.stream()
+        .flatMap(
+            mappedNamePair -> {
+              RetraceClassResultImpl.ElementImpl classElement = mappedNamePair.getFirst();
+              List<MemberNaming> memberNamings = mappedNamePair.getSecond();
+              if (memberNamings == null) {
+                return Stream.of(
+                    new ElementImpl(
+                        this,
+                        classElement,
+                        RetracedFieldImpl.create(
+                            fieldDefinition.substituteHolder(
+                                classElement.getRetracedClass().getClassReference()))));
+              }
+              return memberNamings.stream()
+                  .map(
+                      memberNaming -> {
+                        FieldSignature fieldSignature =
+                            memberNaming.getOriginalSignature().asFieldSignature();
+                        RetracedClassImpl holder =
+                            fieldSignature.isQualified()
+                                ? RetracedClassImpl.create(
+                                    Reference.classFromDescriptor(
+                                        DescriptorUtils.javaTypeToDescriptor(
+                                            fieldSignature.toHolderFromQualified())))
+                                : classElement.getRetracedClass();
+                        return new ElementImpl(
+                            this,
+                            classElement,
+                            RetracedFieldImpl.create(
+                                Reference.field(
+                                    holder.getClassReference(),
+                                    fieldSignature.isQualified()
+                                        ? fieldSignature.toUnqualifiedName()
+                                        : fieldSignature.name,
+                                    Reference.typeFromTypeName(fieldSignature.type))));
+                      });
+            });
+  }
+
+  @Override
+  public RetraceFieldResultImpl forEach(Consumer<Element> resultConsumer) {
+    stream().forEach(resultConsumer);
+    return this;
+  }
+
+  @Override
+  public boolean isAmbiguous() {
+    if (memberNamings.size() > 1) {
+      return true;
+    }
+    List<MemberNaming> mappings = memberNamings.get(0).getSecond();
+    if (mappings == null) {
+      return false;
+    }
+    return mappings.size() > 1;
+  }
+
+  public static class ElementImpl implements RetraceFieldResult.Element {
+
+    private final RetracedFieldImpl fieldReference;
+    private final RetraceFieldResultImpl retraceFieldResult;
+    private final RetraceClassResultImpl.ElementImpl classElement;
+
+    private ElementImpl(
+        RetraceFieldResultImpl retraceFieldResult,
+        RetraceClassResultImpl.ElementImpl classElement,
+        RetracedFieldImpl fieldReference) {
+      this.classElement = classElement;
+      this.fieldReference = fieldReference;
+      this.retraceFieldResult = retraceFieldResult;
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return fieldReference.isUnknown();
+    }
+
+    @Override
+    public RetracedFieldImpl getField() {
+      return fieldReference;
+    }
+
+    @Override
+    public RetraceFieldResultImpl getRetraceFieldResult() {
+      return retraceFieldResult;
+    }
+
+    @Override
+    public RetraceClassResultImpl.ElementImpl getClassElement() {
+      return classElement;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
new file mode 100644
index 0000000..24c5a8e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -0,0 +1,203 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import static com.android.tools.r8.retrace.internal.RetraceUtils.methodReferenceFromMappedRange;
+
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetracedClassMember;
+import com.android.tools.r8.retrace.RetracedMethod;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+public class RetraceFrameResultImpl implements RetraceFrameResult {
+
+  private final RetraceClassResultImpl classResult;
+  private final MethodDefinition methodDefinition;
+  private final int obfuscatedPosition;
+  private final List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> mappedRanges;
+  private final RetracerImpl retracer;
+
+  public RetraceFrameResultImpl(
+      RetraceClassResultImpl classResult,
+      List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> mappedRanges,
+      MethodDefinition methodDefinition,
+      int obfuscatedPosition,
+      RetracerImpl retracer) {
+    this.classResult = classResult;
+    this.methodDefinition = methodDefinition;
+    this.obfuscatedPosition = obfuscatedPosition;
+    this.mappedRanges = mappedRanges;
+    this.retracer = retracer;
+  }
+
+  @Override
+  public boolean isAmbiguous() {
+    if (mappedRanges.size() > 1) {
+      return true;
+    }
+    List<MappedRange> methodRanges = mappedRanges.get(0).getSecond();
+    if (methodRanges == null || methodRanges.isEmpty()) {
+      return false;
+    }
+    MappedRange lastRange = methodRanges.get(0);
+    for (MappedRange mappedRange : methodRanges) {
+      if (mappedRange != lastRange
+          && (mappedRange.minifiedRange == null
+              || !mappedRange.minifiedRange.equals(lastRange.minifiedRange))) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public Stream<Element> stream() {
+    return mappedRanges.stream()
+        .flatMap(
+            mappedRangePair -> {
+              RetraceClassResultImpl.ElementImpl classElement = mappedRangePair.getFirst();
+              List<MappedRange> mappedRanges = mappedRangePair.getSecond();
+              if (mappedRanges == null || mappedRanges.isEmpty()) {
+                return Stream.of(
+                    new ElementImpl(
+                        this,
+                        classElement,
+                        RetracedMethodImpl.create(
+                            methodDefinition.substituteHolder(
+                                classElement.getRetracedClass().getClassReference())),
+                        ImmutableList.of(),
+                        obfuscatedPosition));
+              }
+              // Iterate over mapped ranges that may have different positions than specified.
+              List<ElementImpl> ambiguousFrames = new ArrayList<>();
+              Range minifiedRange = mappedRanges.get(0).minifiedRange;
+              List<MappedRange> mappedRangesForElement = Lists.newArrayList(mappedRanges.get(0));
+              for (int i = 1; i < mappedRanges.size(); i++) {
+                MappedRange mappedRange = mappedRanges.get(i);
+                if (minifiedRange == null || !minifiedRange.equals(mappedRange.minifiedRange)) {
+                  // This is a new frame
+                  ambiguousFrames.add(
+                      elementFromMappedRanges(mappedRangesForElement, classElement));
+                  mappedRangesForElement = new ArrayList<>();
+                }
+                mappedRangesForElement.add(mappedRange);
+              }
+              ambiguousFrames.add(elementFromMappedRanges(mappedRangesForElement, classElement));
+              return ambiguousFrames.stream();
+            });
+  }
+
+  private ElementImpl elementFromMappedRanges(
+      List<MappedRange> mappedRangesForElement, RetraceClassResultImpl.ElementImpl classElement) {
+    MappedRange topFrame = mappedRangesForElement.get(0);
+    MethodReference methodReference =
+        methodReferenceFromMappedRange(
+            topFrame, classElement.getRetracedClass().getClassReference());
+    return new ElementImpl(
+        this,
+        classElement,
+        getRetracedMethod(methodReference, topFrame, obfuscatedPosition),
+        mappedRangesForElement,
+        obfuscatedPosition);
+  }
+
+  @Override
+  public RetraceFrameResultImpl forEach(Consumer<Element> resultConsumer) {
+    stream().forEach(resultConsumer);
+    return this;
+  }
+
+  private RetracedMethodImpl getRetracedMethod(
+      MethodReference methodReference, MappedRange mappedRange, int obfuscatedPosition) {
+    if (obfuscatedPosition == -1
+        || mappedRange.minifiedRange == null
+        || !mappedRange.minifiedRange.contains(obfuscatedPosition)) {
+      return RetracedMethodImpl.create(methodReference);
+    }
+    return RetracedMethodImpl.create(
+        methodReference, mappedRange.getOriginalLineNumber(obfuscatedPosition));
+  }
+
+  public static class ElementImpl implements RetraceFrameResult.Element {
+
+    private final RetracedMethodImpl methodReference;
+    private final RetraceFrameResultImpl retraceFrameResult;
+    private final RetraceClassResultImpl.ElementImpl classElement;
+    private final List<MappedRange> mappedRanges;
+    private final int obfuscatedPosition;
+
+    public ElementImpl(
+        RetraceFrameResultImpl retraceFrameResult,
+        RetraceClassResultImpl.ElementImpl classElement,
+        RetracedMethodImpl methodReference,
+        List<MappedRange> mappedRanges,
+        int obfuscatedPosition) {
+      this.methodReference = methodReference;
+      this.retraceFrameResult = retraceFrameResult;
+      this.classElement = classElement;
+      this.mappedRanges = mappedRanges;
+      this.obfuscatedPosition = obfuscatedPosition;
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return methodReference.isUnknown();
+    }
+
+    @Override
+    public RetracedMethodImpl getTopFrame() {
+      return methodReference;
+    }
+
+    @Override
+    public RetraceClassResultImpl.ElementImpl getClassElement() {
+      return classElement;
+    }
+
+    @Override
+    public void visitFrames(BiConsumer<RetracedMethod, Integer> consumer) {
+      int counter = 0;
+      consumer.accept(getTopFrame(), counter++);
+      for (RetracedMethodImpl outerFrame : getOuterFrames()) {
+        consumer.accept(outerFrame, counter++);
+      }
+    }
+
+    @Override
+    public RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile) {
+      return RetraceUtils.getSourceFile(
+          classElement, frame.getHolderClass(), sourceFile, retraceFrameResult.retracer);
+    }
+
+    @Override
+    public List<RetracedMethodImpl> getOuterFrames() {
+      if (mappedRanges == null) {
+        return Collections.emptyList();
+      }
+      List<RetracedMethodImpl> outerFrames = new ArrayList<>();
+      for (int i = 1; i < mappedRanges.size(); i++) {
+        MappedRange mappedRange = mappedRanges.get(i);
+        MethodReference methodReference =
+            methodReferenceFromMappedRange(
+                mappedRange, classElement.getRetracedClass().getClassReference());
+        outerFrames.add(
+            retraceFrameResult.getRetracedMethod(methodReference, mappedRange, obfuscatedPosition));
+      }
+      return outerFrames;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceInvalidStackTraceLineDiagnostics.java
similarity index 93%
rename from src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
rename to src/main/java/com/android/tools/r8/retrace/internal/RetraceInvalidStackTraceLineDiagnostics.java
index 959976a..547534f 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceInvalidStackTraceLineDiagnostics.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.retrace;
+package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.Keep;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
new file mode 100644
index 0000000..93fa544
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
@@ -0,0 +1,174 @@
+// 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.internal;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+@Keep
+public class RetraceMethodResultImpl implements RetraceMethodResult {
+
+  private final MethodDefinition methodDefinition;
+  private final RetraceClassResultImpl classResult;
+  private final List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> mappedRanges;
+  private final RetracerImpl retracer;
+
+  RetraceMethodResultImpl(
+      RetraceClassResultImpl classResult,
+      List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> mappedRanges,
+      MethodDefinition methodDefinition,
+      RetracerImpl retracer) {
+    this.classResult = classResult;
+    this.mappedRanges = mappedRanges;
+    this.methodDefinition = methodDefinition;
+    this.retracer = retracer;
+    assert classResult != null;
+    assert !mappedRanges.isEmpty();
+  }
+
+  @Override
+  public boolean isAmbiguous() {
+    if (mappedRanges.size() > 1) {
+      return true;
+    }
+    List<MappedRange> methodRanges = mappedRanges.get(0).getSecond();
+    if (methodRanges == null || methodRanges.isEmpty()) {
+      return false;
+    }
+    MappedRange lastRange = methodRanges.get(0);
+    for (MappedRange mappedRange : methodRanges) {
+      if (mappedRange != lastRange
+          && (mappedRange.minifiedRange == null
+              || !mappedRange.minifiedRange.equals(lastRange.minifiedRange))) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public RetraceFrameResultImpl narrowByPosition(int position) {
+    List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> narrowedRanges =
+        new ArrayList<>();
+    List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> noMappingRanges =
+        new ArrayList<>();
+    for (Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>> mappedRange : mappedRanges) {
+      if (mappedRange.getSecond() == null) {
+        noMappingRanges.add(new Pair<>(mappedRange.getFirst(), null));
+        continue;
+      }
+      List<MappedRange> ranges =
+          new MappedRangesOfName(mappedRange.getSecond()).allRangesForLine(position, false);
+      boolean hasAddedRanges = false;
+      if (!ranges.isEmpty()) {
+        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), ranges));
+        hasAddedRanges = true;
+      } else {
+        narrowedRanges = new ArrayList<>();
+        for (MappedRange mapped : mappedRange.getSecond()) {
+          if (mapped.minifiedRange == null) {
+            narrowedRanges.add(new Pair<>(mappedRange.getFirst(), ImmutableList.of(mapped)));
+            hasAddedRanges = true;
+          }
+        }
+      }
+      if (!hasAddedRanges) {
+        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), null));
+      }
+    }
+    return new RetraceFrameResultImpl(
+        classResult,
+        narrowedRanges.isEmpty() ? noMappingRanges : narrowedRanges,
+        methodDefinition,
+        position,
+        retracer);
+  }
+
+  @Override
+  public Stream<Element> stream() {
+    return mappedRanges.stream()
+        .flatMap(
+            mappedRangePair -> {
+              RetraceClassResultImpl.ElementImpl classElement = mappedRangePair.getFirst();
+              List<MappedRange> mappedRanges = mappedRangePair.getSecond();
+              if (mappedRanges == null || mappedRanges.isEmpty()) {
+                return Stream.of(
+                    new ElementImpl(
+                        this,
+                        classElement,
+                        RetracedMethodImpl.create(
+                            methodDefinition.substituteHolder(
+                                classElement.getRetracedClass().getClassReference()))));
+              }
+              return mappedRanges.stream()
+                  .map(
+                      mappedRange -> {
+                        MethodReference methodReference =
+                            RetraceUtils.methodReferenceFromMappedRange(
+                                mappedRange, classElement.getRetracedClass().getClassReference());
+                        return new ElementImpl(
+                            this, classElement, RetracedMethodImpl.create(methodReference));
+                      });
+            });
+  }
+
+  @Override
+  public RetraceMethodResultImpl forEach(Consumer<Element> resultConsumer) {
+    stream().forEach(resultConsumer);
+    return this;
+  }
+
+  public static class ElementImpl implements RetraceMethodResult.Element {
+
+    private final RetracedMethodImpl methodReference;
+    private final RetraceMethodResultImpl retraceMethodResult;
+    private final RetraceClassResultImpl.ElementImpl classElement;
+
+    private ElementImpl(
+        RetraceMethodResultImpl retraceMethodResult,
+        RetraceClassResultImpl.ElementImpl classElement,
+        RetracedMethodImpl methodReference) {
+      this.classElement = classElement;
+      this.retraceMethodResult = retraceMethodResult;
+      this.methodReference = methodReference;
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return methodReference.isUnknown();
+    }
+
+    @Override
+    public com.android.tools.r8.retrace.RetracedMethod getRetracedMethod() {
+      return null;
+    }
+
+    @Override
+    public RetraceMethodResultImpl getRetraceMethodResult() {
+      return retraceMethodResult;
+    }
+
+    @Override
+    public RetraceClassResultImpl.ElementImpl getClassElement() {
+      return classElement;
+    }
+
+    @Override
+    public com.android.tools.r8.retrace.RetraceSourceFileResult retraceSourceFile(
+        String sourceFile) {
+      return RetraceUtils.getSourceFile(
+          classElement, methodReference.getHolderClass(), sourceFile, retraceMethodResult.retracer);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
similarity index 91%
rename from src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
rename to src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
index 910db1a..6ba4d1a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
@@ -2,18 +2,25 @@
 // 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;
+package com.android.tools.r8.retrace.internal;
 
-import static com.android.tools.r8.retrace.RetraceUtils.methodDescriptionFromRetraceMethod;
+import static com.android.tools.r8.retrace.internal.RetraceUtils.methodDescriptionFromRetraceMethod;
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.references.ClassReference;
 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.ClassNameGroup.ClassNameGroupHandler;
+import com.android.tools.r8.retrace.RetraceClassResult;
+import com.android.tools.r8.retrace.RetraceFieldResult;
+import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.retrace.RetraceFrameResult.Element;
+import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetracedClass;
+import com.android.tools.r8.retrace.RetracedField;
 import com.android.tools.r8.retrace.RetracedField.KnownRetracedField;
+import com.android.tools.r8.retrace.RetracedMethod;
+import com.android.tools.r8.retrace.internal.RetraceRegularExpression.ClassNameGroup.ClassNameGroupHandler;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -30,7 +37,7 @@
 
 public class RetraceRegularExpression {
 
-  private final RetraceApi retracer;
+  private final RetracerImpl retracer;
   private final List<String> stackTrace;
   private final DiagnosticsHandler diagnosticsHandler;
   private final String regularExpression;
@@ -52,8 +59,8 @@
   private static final String CAPTURE_GROUP_PREFIX = "captureGroup";
   private static final int FIRST_CAPTURE_GROUP_INDEX = 0;
 
-  RetraceRegularExpression(
-      RetraceApi retracer,
+  public RetraceRegularExpression(
+      RetracerImpl retracer,
       List<String> stackTrace,
       DiagnosticsHandler diagnosticsHandler,
       String regularExpression,
@@ -134,7 +141,7 @@
               case CLASS:
                 return line.getClassContext().getRetracedClass().getTypeName();
               case METHOD:
-                return line.getMethodContext().getMember().getMethodName();
+                return line.getMethodContext().getTopFrame().getMethodName();
               case SOURCE:
                 return line.getSource();
               case LINE:
@@ -241,8 +248,8 @@
   }
 
   static class RetraceStringContext {
-    private final Element classContext;
-    private final RetraceClassMemberElement<? extends RetracedMethod> methodContext;
+    private final RetraceClassResult.Element classContext;
+    private final RetraceFrameResult.Element methodContext;
     private final RetracedClass qualifiedClassContext;
     private final RetracedMethod qualifiedMethodContext;
     private final String methodName;
@@ -252,8 +259,8 @@
     private final boolean isAmbiguous;
 
     private RetraceStringContext(
-        Element classContext,
-        RetraceClassMemberElement<? extends RetracedMethod> methodContext,
+        RetraceClassResult.Element classContext,
+        RetraceFrameResult.Element methodContext,
         RetracedClass qualifiedClassContext,
         RetracedMethod qualifiedMethodContext,
         String methodName,
@@ -278,7 +285,7 @@
     }
 
     private RetraceStringContext withClassContext(
-        Element classContext, RetracedClass qualifiedContext) {
+        RetraceClassResult.Element classContext, RetracedClass qualifiedContext) {
       return new RetraceStringContext(
           classContext,
           methodContext,
@@ -304,8 +311,7 @@
           isAmbiguous);
     }
 
-    private RetraceStringContext withMethodContext(
-        RetraceClassMemberElement<? extends RetracedMethod> methodContext, boolean isAmbiguous) {
+    private RetraceStringContext withMethodContext(Element methodContext, boolean isAmbiguous) {
       return new RetraceStringContext(
           classContext,
           methodContext,
@@ -403,11 +409,11 @@
       this.context = context;
     }
 
-    private Element getClassContext() {
+    private RetraceClassResult.Element getClassContext() {
       return context.classContext;
     }
 
-    private RetraceClassMemberElement<? extends RetracedMethod> getMethodContext() {
+    private RetraceFrameResult.Element getMethodContext() {
       return context.methodContext;
     }
 
@@ -448,10 +454,10 @@
   private interface RegularExpressionGroupHandler {
 
     List<RetraceString> handleMatch(
-        String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer);
+        String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer);
 
     default RetraceStringContext buildInitial(
-        RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+        RetraceStringContext context, Matcher matcher, RetracerImpl retracer) {
       return context;
     }
 
@@ -518,7 +524,7 @@
 
     static class ClassNameGroupHandler implements RegularExpressionGroupHandler {
 
-      private RetraceClassResult retraceClassResult = null;
+      private RetraceClassResultImpl retraceClassResult = null;
       private final ClassNameGroup classNameGroup;
       private final String captureGroup;
       private RegularExpressionGroupHandler qualifiedHandler;
@@ -530,15 +536,15 @@
 
       @Override
       public List<RetraceString> handleMatch(
-          String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+          String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer) {
         final int startOfGroup = matcher.start(captureGroup);
         if (startOfGroup == NO_MATCH) {
           return strings;
         }
         String typeName = matcher.group(captureGroup);
-        RetraceClassResult retraceResult =
+        RetraceClassResultImpl retraceResult =
             retraceClassResult == null
-                ? retracer.retrace(classNameGroup.classFromMatch(typeName))
+                ? retracer.retraceClass(classNameGroup.classFromMatch(typeName))
                 : retraceClassResult;
         assert !retraceResult.isAmbiguous();
         List<RetraceString> retraceStrings = new ArrayList<>(strings.size());
@@ -579,14 +585,14 @@
 
       @Override
       public RetraceStringContext buildInitial(
-          RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+          RetraceStringContext context, Matcher matcher, RetracerImpl retracer) {
         // Reset the local class context since this the same handler is used for multiple lines.
         retraceClassResult = null;
         if (matcher.start(captureGroup) == NO_MATCH || context.classContext != null) {
           return context;
         }
         String typeName = matcher.group(captureGroup);
-        retraceClassResult = retracer.retrace(classNameGroup.classFromMatch(typeName));
+        retraceClassResult = retracer.retraceClass(classNameGroup.classFromMatch(typeName));
         assert !retraceClassResult.isAmbiguous();
         Box<RetraceStringContext> box = new Box<>();
         retraceClassResult.forEach(
@@ -673,7 +679,7 @@
 
         @Override
         public List<RetraceString> handleMatch(
-            String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+            String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer) {
           final int startOfGroup = matcher.start(captureGroup);
           if (startOfGroup == NO_MATCH) {
             if (classNameGroupHandler != null) {
@@ -715,7 +721,7 @@
 
         @Override
         public RetraceStringContext buildInitial(
-            RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+            RetraceStringContext context, Matcher matcher, RetracerImpl retracer) {
           final int startOfGroup = matcher.start(captureGroup);
           if (startOfGroup == NO_MATCH || context.methodName != null) {
             return context;
@@ -728,25 +734,21 @@
     private static void retraceMethodForString(
         RetraceString retraceString,
         String methodName,
-        BiConsumer<RetraceClassMemberElement<? extends RetracedMethod>, RetraceStringContext>
-            process) {
+        BiConsumer<Element, RetraceStringContext> process) {
       if (retraceString.context.classContext == null) {
         return;
       }
-      RetraceMethodResult retraceMethodResult =
-          retraceString.getClassContext().lookupMethod(methodName);
-      Result<? extends RetraceClassMemberElement<RetracedMethod>, ?> retraceResult;
-      if (retraceString.context.minifiedLineNumber > NO_MATCH) {
-        retraceResult =
-            retraceMethodResult.narrowByPosition(retraceString.context.minifiedLineNumber);
-      } else {
-        retraceResult = retraceMethodResult;
-      }
-      retraceResult.forEach(
-          element ->
-              process.accept(
-                  element,
-                  retraceString.context.withMethodContext(element, retraceResult.isAmbiguous())));
+      RetraceClassResult.Element classContext = retraceString.getClassContext();
+      RetraceFrameResult retraceFrameResult =
+          retraceString.context.minifiedLineNumber > NO_MATCH
+              ? classContext.lookupFrame(methodName, retraceString.context.minifiedLineNumber)
+              : classContext.lookupFrame(methodName);
+      retraceFrameResult.forEach(
+          element -> {
+            process.accept(
+                element,
+                retraceString.context.withMethodContext(element, retraceFrameResult.isAmbiguous()));
+          });
     }
   }
 
@@ -776,7 +778,7 @@
 
         @Override
         public List<RetraceString> handleMatch(
-            String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+            String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer) {
           final int startOfGroup = matcher.start(captureGroup);
           if (startOfGroup == NO_MATCH) {
             if (classNameGroupHandler != null) {
@@ -878,7 +880,7 @@
     }
   }
 
-  private class LineNumberGroup extends RegularExpressionGroup {
+  private static class LineNumberGroup extends RegularExpressionGroup {
 
     @Override
     String subExpression() {
@@ -890,7 +892,7 @@
       return new RegularExpressionGroupHandler() {
         @Override
         public List<RetraceString> handleMatch(
-            String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+            String original, List<RetraceString> strings, Matcher matcher, RetracerImpl retracer) {
           final int startOfGroup = matcher.start(captureGroup);
           if (startOfGroup == NO_MATCH) {
             return strings;
@@ -902,8 +904,7 @@
           int lineNumber = Integer.parseInt(lineNumberAsString);
           List<RetraceString> retracedStrings = new ArrayList<>();
           for (RetraceString retraceString : strings) {
-            RetraceClassMemberElement<? extends RetracedMethod> methodContext =
-                retraceString.context.methodContext;
+            Element methodContext = retraceString.context.methodContext;
             if (methodContext == null) {
               if (retraceString.context.classContext == null
                   || retraceString.context.methodName == null) {
@@ -961,7 +962,7 @@
 
         @Override
         public RetraceStringContext buildInitial(
-            RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+            RetraceStringContext context, Matcher matcher, RetracerImpl retracer) {
           if (matcher.start(captureGroup) == NO_MATCH || context.minifiedLineNumber > NO_MATCH) {
             return context;
           }
@@ -1015,12 +1016,12 @@
           return strings;
         }
         TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor);
-        RetraceTypeResult retracedType = retracer.retrace(typeReference);
+        RetraceTypeResultImpl retracedType = retracer.retraceType(typeReference);
         assert !retracedType.isAmbiguous();
         for (RetraceString retraceString : strings) {
           retracedType.forEach(
               element -> {
-                RetracedType retracedReference = element.getType();
+                RetracedTypeImpl retracedReference = element.getType();
                 retraceString.appendRetracedString(
                     original,
                     retracedReference.isVoid() ? "void" : retracedReference.getTypeName(),
@@ -1059,10 +1060,10 @@
                       if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) {
                         return typeName;
                       }
-                      final RetraceTypeResult retraceResult =
-                          retracer.retrace(Reference.returnTypeFromDescriptor(descriptor));
+                      final RetraceTypeResultImpl retraceResult =
+                          retracer.retraceType(Reference.returnTypeFromDescriptor(descriptor));
                       assert !retraceResult.isAmbiguous();
-                      final Box<RetracedType> elementBox = new Box<>();
+                      final Box<RetracedTypeImpl> elementBox = new Box<>();
                       retraceResult.forEach(element -> elementBox.set(element.getType()));
                       return elementBox.get().getTypeName();
                     })
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceSourceFileResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceSourceFileResultImpl.java
new file mode 100644
index 0000000..b84b1a6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceSourceFileResultImpl.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.retrace.RetraceSourceFileResult;
+
+public class RetraceSourceFileResultImpl implements RetraceSourceFileResult {
+
+  private final String filename;
+  private final boolean synthesized;
+
+  RetraceSourceFileResultImpl(String filename, boolean synthesized) {
+    this.filename = filename;
+    this.synthesized = synthesized;
+  }
+
+  @Override
+  public boolean isSynthesized() {
+    return synthesized;
+  }
+
+  @Override
+  public String getFilename() {
+    return filename;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java
new file mode 100644
index 0000000..0680800
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java
@@ -0,0 +1,68 @@
+// 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.internal;
+
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetraceTypeResult;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+public class RetraceTypeResultImpl implements RetraceTypeResult {
+
+  private final TypeReference obfuscatedType;
+  private final RetracerImpl retracer;
+
+  private RetraceTypeResultImpl(TypeReference obfuscatedType, RetracerImpl retracer) {
+    this.obfuscatedType = obfuscatedType;
+    this.retracer = retracer;
+  }
+
+  static RetraceTypeResultImpl create(TypeReference obfuscatedType, RetracerImpl retracer) {
+    return new RetraceTypeResultImpl(obfuscatedType, retracer);
+  }
+
+  @Override
+  public Stream<Element> stream() {
+    // Handle void and primitive types as single element results.
+    if (obfuscatedType == null || obfuscatedType.isPrimitive()) {
+      return Stream.of(new ElementImpl(RetracedTypeImpl.create(obfuscatedType)));
+    }
+    if (obfuscatedType.isArray()) {
+      int dimensions = obfuscatedType.asArray().getDimensions();
+      return retracer.retraceType(obfuscatedType.asArray().getBaseType()).stream()
+          .map(
+              baseElement ->
+                  new ElementImpl(
+                      RetracedTypeImpl.create(baseElement.getType().toArray(dimensions))));
+    }
+    return retracer.retraceClass(obfuscatedType.asClass()).stream()
+        .map(classElement -> new ElementImpl(classElement.getRetracedClass().getRetracedType()));
+  }
+
+  @Override
+  public boolean isAmbiguous() {
+    return false;
+  }
+
+  @Override
+  public RetraceTypeResultImpl forEach(Consumer<Element> resultConsumer) {
+    stream().forEach(resultConsumer);
+    return this;
+  }
+
+  public static class ElementImpl implements RetraceTypeResult.Element {
+
+    private final RetracedTypeImpl retracedType;
+
+    public ElementImpl(RetracedTypeImpl retracedType) {
+      this.retracedType = retracedType;
+    }
+
+    @Override
+    public RetracedTypeImpl getType() {
+      return retracedType;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
similarity index 83%
rename from src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
rename to src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index e402a80..46e8ddc 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.retrace;
+package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -10,6 +10,10 @@
 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.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetracedClass;
+import com.android.tools.r8.retrace.RetracedMethod;
 import com.android.tools.r8.retrace.RetracedMethod.KnownRetracedMethod;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -53,14 +57,14 @@
   }
 
   public static boolean hasPredictableSourceFileName(String originalClassName, String sourceFile) {
-    String synthesizedSourceFileName = getClassSimpleName(originalClassName) + ".java";
+    String synthesizedSourceFileName = getOuterClassSimpleName(originalClassName) + ".java";
     return synthesizedSourceFileName.equals(sourceFile);
   }
 
-  private static String getClassSimpleName(String clazz) {
-    int lastIndexOfPeriod = clazz.lastIndexOf('.');
+  private static String getOuterClassSimpleName(String clazz) {
+    int lastIndexOfPeriod = clazz.lastIndexOf(DescriptorUtils.JAVA_PACKAGE_SEPARATOR);
     // Check if we can find a subclass separator.
-    int endIndex = clazz.lastIndexOf('$');
+    int endIndex = clazz.indexOf(DescriptorUtils.INNER_CLASS_SEPARATOR, lastIndexOfPeriod);
     if (lastIndexOfPeriod > endIndex || endIndex < 0) {
       endIndex = clazz.length();
     }
@@ -68,10 +72,7 @@
   }
 
   static RetraceSourceFileResult getSourceFile(
-      RetraceClassResult.Element classElement,
-      RetracedClass context,
-      String sourceFile,
-      RetraceApi retracer) {
+      Element classElement, RetracedClass context, String sourceFile, RetracerImpl retracer) {
     // If no context is specified always retrace using the found class element.
     if (context == null) {
       return classElement.retraceSourceFile(sourceFile);
@@ -79,7 +80,8 @@
     if (context.equals(classElement.getRetracedClass())) {
       return classElement.retraceSourceFile(sourceFile);
     } else {
-      RetraceClassResult contextClassResult = retracer.retrace(context.getClassReference());
+      RetraceClassResultImpl contextClassResult =
+          retracer.retraceClass(context.getClassReference());
       assert !contextClassResult.isAmbiguous();
       if (contextClassResult.hasRetraceResult()) {
         Box<RetraceSourceFileResult> retraceSourceFile = new Box<>();
@@ -87,7 +89,7 @@
             element -> retraceSourceFile.set(element.retraceSourceFile(sourceFile)));
         return retraceSourceFile.get();
       } else {
-        return new RetraceSourceFileResult(
+        return new RetraceSourceFileResultImpl(
             synthesizeFileName(
                 context.getTypeName(),
                 classElement.getRetracedClass().getTypeName(),
@@ -118,9 +120,9 @@
       // We have no mapping but but file name is unknown, so the best we can do is take the
       // name of the obfuscated clazz.
       assert minifiedClassName.equals(retracedClassName);
-      return getClassSimpleName(minifiedClassName) + "." + extension;
+      return getOuterClassSimpleName(minifiedClassName) + "." + extension;
     }
-    String newFileName = getClassSimpleName(retracedClassName);
+    String newFileName = getOuterClassSimpleName(retracedClassName);
     return newFileName + "." + extension;
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassImpl.java
new file mode 100644
index 0000000..b2da5d8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassImpl.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.retrace.RetracedClass;
+
+@Keep
+public final class RetracedClassImpl implements RetracedClass {
+
+  private final ClassReference classReference;
+
+  private RetracedClassImpl(ClassReference classReference) {
+    assert classReference != null;
+    this.classReference = classReference;
+  }
+
+  public static RetracedClassImpl create(ClassReference classReference) {
+    return new RetracedClassImpl(classReference);
+  }
+
+  @Override
+  public String getTypeName() {
+    return classReference.getTypeName();
+  }
+
+  @Override
+  public String getBinaryName() {
+    return classReference.getBinaryName();
+  }
+
+  @Override
+  public RetracedTypeImpl getRetracedType() {
+    return RetracedTypeImpl.create(classReference);
+  }
+
+  @Override
+  public ClassReference getClassReference() {
+    return classReference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    return classReference.equals(((RetracedClassImpl) o).classReference);
+  }
+
+  @Override
+  public int hashCode() {
+    return classReference.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldImpl.java
new file mode 100644
index 0000000..aee0cde
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldImpl.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetracedField;
+import java.util.Objects;
+
+@Keep
+public abstract class RetracedFieldImpl implements RetracedField {
+
+  private RetracedFieldImpl() {}
+
+  @Override
+  public boolean isUnknown() {
+    return true;
+  }
+
+  @Override
+  public final boolean isKnown() {
+    return !isUnknown();
+  }
+
+  @Override
+  public KnownRetracedFieldImpl asKnown() {
+    return null;
+  }
+
+  public static final class KnownRetracedFieldImpl extends RetracedFieldImpl
+      implements KnownRetracedField {
+
+    private final FieldReference fieldReference;
+
+    private KnownRetracedFieldImpl(FieldReference fieldReference) {
+      this.fieldReference = fieldReference;
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return false;
+    }
+
+    @Override
+    public KnownRetracedFieldImpl asKnown() {
+      return this;
+    }
+
+    @Override
+    public RetracedClassImpl getHolderClass() {
+      return RetracedClassImpl.create(fieldReference.getHolderClass());
+    }
+
+    @Override
+    public String getFieldName() {
+      return fieldReference.getFieldName();
+    }
+
+    @Override
+    public TypeReference getFieldType() {
+      return fieldReference.getFieldType();
+    }
+
+    @Override
+    public FieldReference getFieldReference() {
+      return fieldReference;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      KnownRetracedFieldImpl that = (KnownRetracedFieldImpl) o;
+      return fieldReference.equals(that.fieldReference);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(fieldReference);
+    }
+  }
+
+  public static final class UnknownRetracedField extends RetracedFieldImpl {
+
+    private final FieldDefinition fieldDefinition;
+
+    private UnknownRetracedField(FieldDefinition fieldDefinition) {
+      this.fieldDefinition = fieldDefinition;
+    }
+
+    @Override
+    public RetracedClassImpl getHolderClass() {
+      return RetracedClassImpl.create(fieldDefinition.getHolderClass());
+    }
+
+    @Override
+    public String getFieldName() {
+      return fieldDefinition.getName();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      UnknownRetracedField that = (UnknownRetracedField) o;
+      return fieldDefinition.equals(that.fieldDefinition);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(fieldDefinition);
+    }
+  }
+
+  static RetracedFieldImpl create(FieldReference fieldReference) {
+    return new KnownRetracedFieldImpl(fieldReference);
+  }
+
+  static RetracedFieldImpl create(FieldDefinition fieldDefinition) {
+    return new UnknownRetracedField(fieldDefinition);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java
new file mode 100644
index 0000000..998fe7d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetracedMethod;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+@Keep
+public abstract class RetracedMethodImpl implements RetracedMethod {
+
+  private static final int NO_POSITION = -1;
+
+  private RetracedMethodImpl() {}
+
+  @Override
+  public boolean isUnknown() {
+    return true;
+  }
+
+  @Override
+  public final boolean isKnown() {
+    return !isUnknown();
+  }
+
+  @Override
+  public KnownRetracedMethodImpl asKnown() {
+    return null;
+  }
+
+  public static final class KnownRetracedMethodImpl extends RetracedMethodImpl
+      implements KnownRetracedMethod {
+
+    private final MethodReference methodReference;
+    private final int position;
+
+    private KnownRetracedMethodImpl(MethodReference methodReference, int position) {
+      assert methodReference != null;
+      this.methodReference = methodReference;
+      this.position = position;
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return false;
+    }
+
+    @Override
+    public boolean isVoid() {
+      return methodReference.getReturnType() == null;
+    }
+
+    @Override
+    public KnownRetracedMethodImpl asKnown() {
+      return this;
+    }
+
+    @Override
+    public RetracedClassImpl getHolderClass() {
+      return RetracedClassImpl.create(methodReference.getHolderClass());
+    }
+
+    @Override
+    public String getMethodName() {
+      return methodReference.getMethodName();
+    }
+
+    @Override
+    public boolean hasPosition() {
+      return position != NO_POSITION;
+    }
+
+    @Override
+    public int getOriginalPositionOrDefault(int defaultPosition) {
+      return hasPosition() ? position : defaultPosition;
+    }
+
+    @Override
+    public TypeReference getReturnType() {
+      assert !isVoid();
+      return methodReference.getReturnType();
+    }
+
+    @Override
+    public List<TypeReference> getFormalTypes() {
+      return methodReference.getFormalTypes();
+    }
+
+    @Override
+    public MethodReference getMethodReference() {
+      return methodReference;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      KnownRetracedMethodImpl that = (KnownRetracedMethodImpl) o;
+      return position == that.position && methodReference.equals(that.methodReference);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(methodReference, position);
+    }
+  }
+
+  public static final class UnknownRetracedMethodImpl extends RetracedMethodImpl {
+
+    private final MethodDefinition methodDefinition;
+    private final int position;
+
+    private UnknownRetracedMethodImpl(MethodDefinition methodDefinition, int position) {
+      this.methodDefinition = methodDefinition;
+      this.position = position;
+    }
+
+    @Override
+    public RetracedClassImpl getHolderClass() {
+      return RetracedClassImpl.create(methodDefinition.getHolderClass());
+    }
+
+    @Override
+    public String getMethodName() {
+      return methodDefinition.getName();
+    }
+
+    @Override
+    public boolean hasPosition() {
+      return position != NO_POSITION;
+    }
+
+    @Override
+    public int getOriginalPositionOrDefault(int defaultPosition) {
+      return hasPosition() ? position : defaultPosition;
+    }
+
+    public Optional<MethodReference> getMethodReference() {
+      if (!methodDefinition.isFullMethodDefinition()) {
+        return Optional.empty();
+      }
+      return Optional.of(methodDefinition.asFullMethodDefinition().getMethodReference());
+    }
+  }
+
+  static RetracedMethodImpl create(MethodDefinition methodDefinition) {
+    return create(methodDefinition, NO_POSITION);
+  }
+
+  static RetracedMethodImpl create(MethodDefinition methodDefinition, int position) {
+    if (methodDefinition.isFullMethodDefinition()) {
+      return new KnownRetracedMethodImpl(
+          methodDefinition.asFullMethodDefinition().getMethodReference(), position);
+    }
+    return new UnknownRetracedMethodImpl(methodDefinition, position);
+  }
+
+  static RetracedMethodImpl create(MethodReference methodReference) {
+    return create(methodReference, NO_POSITION);
+  }
+
+  static RetracedMethodImpl create(MethodReference methodReference, int position) {
+    return new KnownRetracedMethodImpl(methodReference, position);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java
new file mode 100644
index 0000000..8162e05
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetracedType;
+import java.util.Objects;
+
+@Keep
+public final class RetracedTypeImpl implements RetracedType {
+
+  private final TypeReference typeReference;
+
+  private RetracedTypeImpl(TypeReference typeReference) {
+    this.typeReference = typeReference;
+  }
+
+  static RetracedTypeImpl create(TypeReference typeReference) {
+    return new RetracedTypeImpl(typeReference);
+  }
+
+  @Override
+  public boolean isVoid() {
+    return typeReference == null;
+  }
+
+  @Override
+  public TypeReference toArray(int dimensions) {
+    return Reference.array(typeReference, dimensions);
+  }
+
+  @Override
+  public String getTypeName() {
+    assert !isVoid();
+    return typeReference.getTypeName();
+  }
+
+  @Override
+  public TypeReference getTypeReference() {
+    return typeReference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    return typeReference.equals(((RetracedTypeImpl) o).typeReference);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(typeReference);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
new file mode 100644
index 0000000..eed4f28
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -0,0 +1,70 @@
+// 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.internal;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.InvalidMappingFileException;
+import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
+import com.android.tools.r8.retrace.Retracer;
+
+/** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
+public class RetracerImpl implements Retracer {
+
+  private final ClassNameMapper classNameMapper;
+
+  private RetracerImpl(ClassNameMapper classNameMapper) {
+    this.classNameMapper = classNameMapper;
+    assert classNameMapper != null;
+  }
+
+  public static RetracerImpl create(
+      ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
+    if (proguardMapProducer instanceof DirectClassNameMapperProguardMapProducer) {
+      return new RetracerImpl(
+          ((DirectClassNameMapperProguardMapProducer) proguardMapProducer).getClassNameMapper());
+    }
+    try {
+      ClassNameMapper classNameMapper =
+          ClassNameMapper.mapperFromString(proguardMapProducer.get(), diagnosticsHandler);
+      return new RetracerImpl(classNameMapper);
+    } catch (Throwable throwable) {
+      throw new InvalidMappingFileException(throwable);
+    }
+  }
+
+  @Override
+  public RetraceMethodResultImpl retraceMethod(MethodReference methodReference) {
+    return retraceClass(methodReference.getHolderClass())
+        .lookupMethod(methodReference.getMethodName());
+  }
+
+  @Override
+  public RetraceFrameResultImpl retraceFrame(MethodReference methodReference, int position) {
+    return retraceClass(methodReference.getHolderClass())
+        .lookupMethod(methodReference.getMethodName())
+        .narrowByPosition(position);
+  }
+
+  @Override
+  public RetraceFieldResultImpl retraceField(FieldReference fieldReference) {
+    return retraceClass(fieldReference.getHolderClass()).lookupField(fieldReference.getFieldName());
+  }
+
+  @Override
+  public RetraceClassResultImpl retraceClass(ClassReference classReference) {
+    return RetraceClassResultImpl.create(
+        classReference, classNameMapper.getClassNaming(classReference.getTypeName()), this);
+  }
+
+  @Override
+  public RetraceTypeResultImpl retraceType(TypeReference typeReference) {
+    return RetraceTypeResultImpl.create(typeReference, this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
new file mode 100644
index 0000000..c21b92d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -0,0 +1,207 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceStackTraceProxy;
+import com.android.tools.r8.retrace.RetracedClass;
+import com.android.tools.r8.retrace.RetracedMethod;
+import com.android.tools.r8.retrace.StackTraceElementProxy;
+import com.android.tools.r8.retrace.StackTraceElementProxyRetracer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+public class StackTraceElementProxyRetracerImpl<T extends StackTraceElementProxy<?>>
+    implements StackTraceElementProxyRetracer<T> {
+
+  private final RetracerImpl retracer;
+
+  public StackTraceElementProxyRetracerImpl(RetracerImpl retracer) {
+    this.retracer = retracer;
+  }
+
+  @Override
+  public Stream<RetraceStackTraceProxyImpl<T>> retrace(T element) {
+    if (!element.hasClassName()) {
+      return Stream.of(RetraceStackTraceProxyImpl.builder(element).build());
+    }
+    RetraceClassResultImpl classResult =
+        retracer.retraceClass(Reference.classFromTypeName(element.className()));
+    List<RetraceStackTraceProxyImpl<T>> retracedProxies = new ArrayList<>();
+    if (!element.hasMethodName()) {
+      classResult.forEach(
+          classElement -> {
+            RetraceStackTraceProxyImpl.Builder<T> proxy =
+                RetraceStackTraceProxyImpl.builder(element)
+                    .setRetracedClassElement(classElement.getRetracedClass())
+                    .setAmbiguous(classResult.isAmbiguous());
+            if (element.hasFileName()) {
+              proxy.setSourceFile(classElement.retraceSourceFile(element.fileName()).getFilename());
+            }
+            retracedProxies.add(proxy.build());
+          });
+    } else {
+      RetraceFrameResultImpl frameResult =
+          element.hasLineNumber()
+              ? classResult.lookupFrame(element.methodName(), element.lineNumber())
+              : classResult.lookupFrame(element.methodName());
+      frameResult.forEach(
+          frameElement -> {
+            frameElement.visitFrames(
+                (frame, index) -> {
+                  RetraceStackTraceProxyImpl.Builder<T> proxy =
+                      RetraceStackTraceProxyImpl.builder(element)
+                          .setRetracedClassElement(frame.getHolderClass())
+                          .setRetracedMethodElement(frame)
+                          .setAmbiguous(frameResult.isAmbiguous() && index == 0);
+                  if (element.hasLineNumber()) {
+                    proxy.setLineNumber(frame.getOriginalPositionOrDefault(element.lineNumber()));
+                  }
+                  if (element.hasFileName()) {
+                    proxy.setSourceFile(
+                        frameElement.retraceSourceFile(frame, element.fileName()).getFilename());
+                  }
+                  retracedProxies.add(proxy.build());
+                });
+          });
+    }
+    return retracedProxies.stream();
+  }
+
+  public static class RetraceStackTraceProxyImpl<T extends StackTraceElementProxy<?>>
+      implements RetraceStackTraceProxy<T> {
+
+    private final T originalItem;
+    private final RetracedClass retracedClass;
+    private final RetracedMethod retracedMethod;
+    private final String sourceFile;
+    private final int lineNumber;
+    private final boolean isAmbiguous;
+
+    private RetraceStackTraceProxyImpl(
+        T originalItem,
+        RetracedClass retracedClass,
+        RetracedMethod retracedMethod,
+        String sourceFile,
+        int lineNumber,
+        boolean isAmbiguous) {
+      assert originalItem != null;
+      this.originalItem = originalItem;
+      this.retracedClass = retracedClass;
+      this.retracedMethod = retracedMethod;
+      this.sourceFile = sourceFile;
+      this.lineNumber = lineNumber;
+      this.isAmbiguous = isAmbiguous;
+    }
+
+    @Override
+    public boolean isAmbiguous() {
+      return isAmbiguous;
+    }
+
+    @Override
+    public boolean hasRetracedClass() {
+      return retracedClass != null;
+    }
+
+    @Override
+    public boolean hasRetracedMethod() {
+      return retracedMethod != null;
+    }
+
+    @Override
+    public boolean hasSourceFile() {
+      return sourceFile != null;
+    }
+
+    @Override
+    public boolean hasLineNumber() {
+      return lineNumber != -1;
+    }
+
+    @Override
+    public T getOriginalItem() {
+      return originalItem;
+    }
+
+    @Override
+    public RetracedClass getRetracedClass() {
+      return retracedClass;
+    }
+
+    @Override
+    public RetracedMethod getRetracedMethod() {
+      return retracedMethod;
+    }
+
+    @Override
+    public String getSourceFile() {
+      return sourceFile;
+    }
+
+    private static <T extends StackTraceElementProxy<?>> Builder<T> builder(T originalElement) {
+      return new Builder<>(originalElement);
+    }
+
+    @Override
+    public int getLineNumber() {
+      return lineNumber;
+    }
+
+    private static class Builder<T extends StackTraceElementProxy<?>> {
+
+      private final T originalElement;
+      private RetracedClass classContext;
+      private RetracedMethod methodContext;
+      private String sourceFile;
+      private int lineNumber = -1;
+      private boolean isAmbiguous;
+
+      private Builder(T originalElement) {
+        this.originalElement = originalElement;
+      }
+
+      private Builder<T> setRetracedClassElement(RetracedClass retracedClass) {
+        this.classContext = retracedClass;
+        return this;
+      }
+
+      private Builder<T> setRetracedMethodElement(RetracedMethod methodElement) {
+        this.methodContext = methodElement;
+        return this;
+      }
+
+      private Builder<T> setSourceFile(String sourceFile) {
+        this.sourceFile = sourceFile;
+        return this;
+      }
+
+      private Builder<T> setLineNumber(int lineNumber) {
+        this.lineNumber = lineNumber;
+        return this;
+      }
+
+      private Builder<T> setAmbiguous(boolean ambiguous) {
+        this.isAmbiguous = ambiguous;
+        return this;
+      }
+
+      private RetraceStackTraceProxyImpl<T> build() {
+        RetracedClass retracedClass = classContext;
+        if (methodContext != null) {
+          retracedClass = methodContext.getHolderClass();
+        }
+        return new RetraceStackTraceProxyImpl<>(
+            originalElement,
+            retracedClass,
+            methodContext,
+            sourceFile != null ? sourceFile : null,
+            lineNumber,
+            isAmbiguous);
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/retrace/StackTraceElementStringProxy.java
rename to src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index dc21cbe..202d297 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -2,12 +2,13 @@
 // 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;
+package com.android.tools.r8.retrace.internal;
 
-import static com.android.tools.r8.retrace.PlainStackTraceVisitor.firstNonWhiteSpaceCharacterFromIndex;
-import static com.android.tools.r8.retrace.StackTraceElementStringProxy.StringIndex.noIndex;
+import static com.android.tools.r8.retrace.internal.PlainStackTraceVisitor.firstNonWhiteSpaceCharacterFromIndex;
+import static com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StringIndex.noIndex;
 
-import com.android.tools.r8.retrace.StackTraceElementProxyRetracer.RetraceStackTraceProxy;
+import com.android.tools.r8.retrace.StackTraceElementProxy;
+import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl.RetraceStackTraceProxyImpl;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.BiFunction;
@@ -88,7 +89,8 @@
   }
 
   public String toRetracedItem(
-      RetraceStackTraceProxy<StackTraceElementStringProxy> retracedProxy, boolean printAmbiguous) {
+      RetraceStackTraceProxyImpl<StackTraceElementStringProxy> retracedProxy,
+      boolean printAmbiguous) {
     StringBuilder sb = new StringBuilder();
     int lastSeenIndex = 0;
     if (retracedProxy.isAmbiguous() && printAmbiguous) {
@@ -203,7 +205,7 @@
     private final int startIndex;
     private final int endIndex;
     private final BiFunction<
-            RetraceStackTraceProxy<StackTraceElementStringProxy>,
+            RetraceStackTraceProxyImpl<StackTraceElementStringProxy>,
             StackTraceElementStringProxy,
             String>
         retracedString;
@@ -212,7 +214,7 @@
         int startIndex,
         int endIndex,
         BiFunction<
-                RetraceStackTraceProxy<StackTraceElementStringProxy>,
+                RetraceStackTraceProxyImpl<StackTraceElementStringProxy>,
                 StackTraceElementStringProxy,
                 String>
             retracedString) {
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceVisitor.java
similarity index 77%
rename from src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java
rename to src/main/java/com/android/tools/r8/retrace/internal/StackTraceVisitor.java
index fd9c822..dfe937a 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceVisitor.java
@@ -2,8 +2,9 @@
 // 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;
+package com.android.tools.r8.retrace.internal;
 
+import com.android.tools.r8.retrace.StackTraceElementProxy;
 import java.util.function.Consumer;
 
 public interface StackTraceVisitor<T extends StackTraceElementProxy<?>> {
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 d5bc317..7c6d911 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -905,6 +905,10 @@
     return this;
   }
 
+  public boolean isClassInliningAllowed(DexProgramClass clazz) {
+    return !isPinned(clazz) && !neverClassInline.contains(clazz.getType());
+  }
+
   public boolean isMinificationAllowed(DexReference reference) {
     return options().isMinificationEnabled()
         && keepInfo.getInfo(reference, this).isMinificationAllowed(options());
@@ -938,6 +942,11 @@
     return keepInfo.isPinned(reference, this);
   }
 
+  public boolean isPinned(DexDefinition definition) {
+    assert definition != null;
+    return isPinned(definition.toReference());
+  }
+
   public boolean hasPinnedInstanceInitializer(DexType type) {
     assert type.isClassType();
     DexProgramClass clazz = asProgramClassOrNull(definitionFor(type));
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index d0834d5..9ccbf5e 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -223,7 +223,10 @@
       keepClassInfo.forEach(
           (type, info) -> {
             DexType newType = lens.lookupType(type);
-            assert newType == type || !info.isPinned() || info.isMinificationAllowed(options);
+            assert newType == type
+                || !info.isPinned()
+                || info.isMinificationAllowed(options)
+                || info.isRepackagingAllowed(options);
             KeepClassInfo previous = newClassInfo.put(newType, info);
             assert previous == null;
           });
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 28d1ed5..32ca59a 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -166,16 +166,28 @@
   public static void withMainProgramHandler(MainAction action) {
     try {
       action.run();
-    } catch (CompilationFailedException | AbortException e) {
+    } catch (CompilationFailedException e) {
+      throw exitWithError(e, e.getCause());
+    } catch (RuntimeException e) {
+      throw exitWithError(e, e);
+    }
+  }
+
+  private static RuntimeException exitWithError(Throwable e, Throwable cause) {
+    if (isExpectedException(cause)) {
       // Detail of the errors were already reported
       System.err.println("Compilation failed");
       System.exit(STATUS_ERROR);
-    } catch (RuntimeException e) {
-      System.err.println("Compilation failed with an internal error.");
-      Throwable cause = e.getCause() == null ? e : e.getCause();
-      cause.printStackTrace();
-      System.exit(STATUS_ERROR);
+      throw null;
     }
+    System.err.println("Compilation failed with an internal error.");
+    e.printStackTrace();
+    System.exit(STATUS_ERROR);
+    throw null;
+  }
+
+  private static boolean isExpectedException(Throwable e) {
+    return e instanceof CompilationError || e instanceof AbortException;
   }
 
   // We should try to avoid the use of this extraction as it signifies a point where we don't have
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 c1fe00e..f4c003e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -352,12 +352,10 @@
             .setCompilationMode(debug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
             .setBackend(isGeneratingClassFiles() ? Backend.CF : Backend.DEX)
             .setHasChecksums(encodeChecksums);
-    // Compiling with D8 and L8 is always with a min API level and desugaring to that level. If
-    // desugaring is explicitly turned off for D8 the input is expected to already have been
-    // desugared to the specified min API level. For R8 desugaring is optional.
-    if (tool == Tool.D8
-        || tool == Tool.L8
-        || (tool == Tool.R8 && desugarState != DesugarState.OFF)) {
+    // The marker records the min API if any desugaring happens or if the compiler generates dex
+    // since the output depends on the min API in this case. There is basically no min API entry
+    // in R8 cf to cf.
+    if (isGeneratingDex() || desugarState == DesugarState.ON) {
       marker.setMinApi(minApiLevel);
     }
     if (desugaredLibraryConfiguration.getIdentifier() != null) {
@@ -1307,7 +1305,6 @@
     public PrintStream whyAreYouNotInliningConsumer = System.out;
     public boolean trackDesugaredAPIConversions =
         System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
-    public boolean forceLibBackportsInL8CfToCf = false;
     public boolean enumUnboxingRewriteJavaCGeneratedMethod = false;
     // TODO(b/154793333): Enable assertions always when resolved.
     public boolean assertConsistentRenamingOfSignature = false;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 2272f50..f2358c5 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -48,7 +48,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.Range;
 import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
-import com.android.tools.r8.retrace.RetraceUtils;
+import com.android.tools.r8.retrace.internal.RetraceUtils;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.base.Suppliers;
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 43f4f57..4e476e3 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -11,10 +11,38 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.DiagnosticsLevel;
 import com.android.tools.r8.errors.Unreachable;
+import java.util.ArrayList;
+import java.util.List;
 
 public class Reporter implements DiagnosticsHandler {
 
+  private static class DiagnosticsLevelMapping {
+    private final DiagnosticsLevel from;
+    private final DiagnosticsLevel to;
+    private final String diagnosticsClassName;
+
+    public DiagnosticsLevelMapping(
+        DiagnosticsLevel from, DiagnosticsLevel to, String diagnosticsClassName) {
+      this.from = from;
+      this.to = to;
+      this.diagnosticsClassName = diagnosticsClassName;
+    }
+
+    public DiagnosticsLevel map(DiagnosticsLevel level, Diagnostic diagnostic) {
+      if (level != from) {
+        return level;
+      }
+      if (diagnosticsClassName.length() == 0
+          || diagnosticsClassName.equals(diagnostic.getClass().getSimpleName())
+          || diagnosticsClassName.equals(diagnostic.getClass().getTypeName())) {
+        return to;
+      }
+      return level;
+    }
+  }
+
   private final DiagnosticsHandler clientHandler;
+  private final List<DiagnosticsLevelMapping> diagnosticsLevelMapping = new ArrayList<>();
   private AbortException abort = null;
 
   public Reporter() {
@@ -31,6 +59,7 @@
     if (level != null) {
       DiagnosticsLevel modifiedLevel = clientHandler.modifyDiagnosticsLevel(level, diagnostic);
       level = modifiedLevel != null ? modifiedLevel : level;
+      level = mapDiagnosticsLevel(level, diagnostic);
     } else {
       level = ERROR;
     }
@@ -55,6 +84,10 @@
     handleDiagnostic(INFO, info);
   }
 
+  public void info(String message) {
+    info(new StringDiagnostic(message));
+  }
+
   @Override
   public synchronized void warning(Diagnostic warning) {
     handleDiagnostic(WARNING, warning);
@@ -94,4 +127,16 @@
       throw new RuntimeException(abort);
     }
   }
+
+  private DiagnosticsLevel mapDiagnosticsLevel(DiagnosticsLevel level, Diagnostic diagnostic) {
+    for (DiagnosticsLevelMapping diagnosticsLevelMapping : diagnosticsLevelMapping) {
+      level = diagnosticsLevelMapping.map(level, diagnostic);
+    }
+    return level;
+  }
+
+  public void addDiagnosticsLevelMapping(
+      DiagnosticsLevel from, String diagnosticsClassName, DiagnosticsLevel to) {
+    diagnosticsLevelMapping.add(new DiagnosticsLevelMapping(from, to, diagnosticsClassName));
+  }
 }
diff --git a/src/main/keep.txt b/src/main/keep.txt
index 84423da..f8bbc12 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -21,6 +21,7 @@
 -keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
 
 -keeppackagenames com.android.tools.r8
+-repackageclasses com.android.tools.r8.internal
 
 # Compatibility command line program used by the Android Platform build.
 -keep public class com.android.tools.r8.compatproguard.CompatProguard { public static void main(java.lang.String[]); }
diff --git a/src/test/java/com/android/tools/r8/CommandTestBase.java b/src/test/java/com/android/tools/r8/CommandTestBase.java
new file mode 100644
index 0000000..dcfbeca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/CommandTestBase.java
@@ -0,0 +1,315 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public abstract class CommandTestBase<C extends BaseCompilerCommand> extends TestBase {
+  private void mapDiagnosticsMissingArguments(String... args) throws Exception {
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          "Missing argument(s) for --map-diagnostics", handler -> parse(handler, args));
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void mapDiagnosticsMissingArguments() throws Exception {
+    mapDiagnosticsMissingArguments("--map-diagnostics");
+    mapDiagnosticsMissingArguments("--map-diagnostics", "error");
+    mapDiagnosticsMissingArguments("--map-diagnostics", "warning");
+    mapDiagnosticsMissingArguments("--map-diagnostics", "info");
+    mapDiagnosticsMissingArguments("--map-diagnostics", "xxx");
+  }
+
+  private void mapDiagnosticsInvalidArguments(String... args) throws Exception {
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          "Invalid diagnostics level 'xxx'", handler -> parse(handler, args));
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void mapDiagnosticsInvalidArguments() throws Exception {
+    mapDiagnosticsInvalidArguments("--map-diagnostics", "error", "xxx");
+    mapDiagnosticsInvalidArguments("--map-diagnostics", "warning", "xxx");
+    mapDiagnosticsInvalidArguments("--map-diagnostics", "info", "xxx");
+    mapDiagnosticsInvalidArguments("--map-diagnostics", "xxx", "xxx");
+    mapDiagnosticsInvalidArguments("--map-diagnostics", "xxx", "error");
+    mapDiagnosticsInvalidArguments("--map-diagnostics", "xxx", "warning");
+    mapDiagnosticsInvalidArguments("--map-diagnostics", "xxx", "info");
+    mapDiagnosticsInvalidArguments("--debug", "--map-diagnostics", "error", "xxx", "--debug");
+  }
+
+  @Test
+  public void mapDiagnosticsInvalidArgumentsMoreErrors() throws Exception {
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          ImmutableList.of("Invalid diagnostics level 'xxx'", "Unknown option: --xxx"),
+          handler -> parse(handler, "--debug", "--map-diagnostics", "error", "xxx", "--xxx"));
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void errorsToWarnings() throws Exception {
+    DiagnosticsChecker.checkWarningsContains(
+        "Error",
+        handler -> {
+          C command = parseWithRequiredArgs(handler, "--map-diagnostics", "error", "warning");
+          command.getReporter().error("Error");
+        });
+
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          "Test diagnostic",
+          handler -> {
+            C command =
+                parseWithRequiredArgs(handler, "--map-diagnostics:a.b.C", "error", "warning");
+            command.getReporter().error(new TestDiagnostic());
+            try {
+              command.getReporter().failIfPendingErrors();
+            } catch (RuntimeException e) {
+              throw new CompilationFailedException();
+            }
+          });
+      fail("Failure expected");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+
+    DiagnosticsChecker.checkWarningsContains(
+        "Test diagnostic",
+        handler -> {
+          C command =
+              parseWithRequiredArgs(
+                  handler,
+                  "--map-diagnostics:com.android.tools.r8.TestDiagnostic",
+                  "error",
+                  "warning");
+          command.getReporter().error(new TestDiagnostic());
+        });
+
+    DiagnosticsChecker.checkWarningsContains(
+        "Test diagnostic",
+        handler -> {
+          C command =
+              parseWithRequiredArgs(
+                  handler, "--map-diagnostics:TestDiagnostic", "error", "warning");
+          command.getReporter().error(new TestDiagnostic());
+        });
+  }
+
+  @Test
+  public void errorsToInfo() throws Exception {
+    DiagnosticsChecker.checkInfosContains(
+        "Error",
+        handler -> {
+          C command = parseWithRequiredArgs(handler, "--map-diagnostics", "error", "info");
+          command.getReporter().error("Error");
+        });
+
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          "Test diagnostic",
+          handler -> {
+            C command = parseWithRequiredArgs(handler, "--map-diagnostics:a.b.C", "error", "info");
+            command.getReporter().error(new TestDiagnostic());
+            try {
+              command.getReporter().failIfPendingErrors();
+            } catch (RuntimeException e) {
+              throw new CompilationFailedException();
+            }
+          });
+      fail("Failure expected");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+
+    DiagnosticsChecker.checkInfosContains(
+        "Test diagnostic",
+        handler -> {
+          C command =
+              parseWithRequiredArgs(handler, "--map-diagnostics:TestDiagnostic", "error", "info");
+          command.getReporter().error(new TestDiagnostic());
+        });
+  }
+
+  @Test
+  public void warningsToInfo() throws Exception {
+    DiagnosticsChecker.checkInfosContains(
+        "Warning",
+        handler -> {
+          C command = parseWithRequiredArgs(handler, "--map-diagnostics", "warning", "info");
+          command.getReporter().warning("Warning");
+        });
+
+    DiagnosticsChecker.checkInfosContains(
+        "Test diagnostic",
+        handler -> {
+          C command =
+              parseWithRequiredArgs(handler, "--map-diagnostics:TestDiagnostic", "warning", "info");
+          command.getReporter().warning(new TestDiagnostic());
+        });
+  }
+
+  @Test
+  public void warningsToError() throws Exception {
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          "Warning",
+          handler -> {
+            C command = parseWithRequiredArgs(handler, "--map-diagnostics", "warning", "error");
+            command.getReporter().warning("Warning");
+            try {
+              command.getReporter().failIfPendingErrors();
+            } catch (RuntimeException e) {
+              throw new CompilationFailedException();
+            }
+          });
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          "Test diagnostic",
+          handler -> {
+            C command =
+                parseWithRequiredArgs(
+                    handler, "--map-diagnostics:TestDiagnostic", "warning", "error");
+            command.getReporter().warning(new TestDiagnostic());
+            try {
+              command.getReporter().failIfPendingErrors();
+            } catch (RuntimeException e) {
+              throw new CompilationFailedException();
+            }
+          });
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void errorsToWarningsWarningsToInfos() throws Exception {
+    DiagnosticsChecker.checkInfosContains(
+        "Error",
+        handler -> {
+          C command =
+              parseWithRequiredArgs(
+                  handler,
+                  "--map-diagnostics",
+                  "error",
+                  "warning",
+                  "--map-diagnostics",
+                  "warning",
+                  "info");
+          command.getReporter().error("Error");
+        });
+
+    DiagnosticsChecker.checkInfosContains(
+        "Test diagnostic",
+        handler -> {
+          C command =
+              parseWithRequiredArgs(
+                  handler,
+                  "--map-diagnostics:TestDiagnostic",
+                  "error",
+                  "warning",
+                  "--map-diagnostics",
+                  "warning",
+                  "info");
+          command.getReporter().error(new TestDiagnostic());
+        });
+  }
+
+  @Test
+  public void warningsToInfosErrorsToWarnings() throws Exception {
+    DiagnosticsChecker.checkDiagnostics(
+        diagnostics -> {
+          assertEquals(0, diagnostics.errors.size());
+          diagnostics.checkWarningsContains("Error");
+          diagnostics.checkInfosContains("Warning");
+        },
+        handler -> {
+          C command =
+              parseWithRequiredArgs(
+                  handler,
+                  "--map-diagnostics",
+                  "warning",
+                  "info",
+                  "--map-diagnostics",
+                  "error",
+                  "warning");
+          command.getReporter().error("Error");
+          command.getReporter().warning("Warning");
+        });
+
+    DiagnosticsChecker.checkDiagnostics(
+        diagnostics -> {
+          assertEquals(0, diagnostics.errors.size());
+          diagnostics.checkWarningsContains("Test diagnostic");
+          diagnostics.checkInfosContains("Test diagnostic");
+        },
+        handler -> {
+          C command =
+              parseWithRequiredArgs(
+                  handler,
+                  "--map-diagnostics:TestDiagnostic",
+                  "warning",
+                  "info",
+                  "--map-diagnostics:TestDiagnostic",
+                  "error",
+                  "warning");
+          command.getReporter().error(new TestDiagnostic());
+          command.getReporter().warning(new TestDiagnostic());
+        });
+  }
+
+  private String[] prepareArgs(String[] args) {
+    String[] actualTestArgs;
+    String[] additionalTestArgs = requiredArgsForTest();
+    if (additionalTestArgs.length > 0) {
+      actualTestArgs = new String[args.length + additionalTestArgs.length];
+      System.arraycopy(additionalTestArgs, 0, actualTestArgs, 0, additionalTestArgs.length);
+      System.arraycopy(args, 0, actualTestArgs, additionalTestArgs.length, args.length);
+    } else {
+      actualTestArgs = args;
+    }
+    return actualTestArgs;
+  }
+
+  private C parseWithRequiredArgs(String... args) throws CompilationFailedException {
+    return parse(prepareArgs(args));
+  }
+
+  private C parseWithRequiredArgs(DiagnosticsHandler handler, String... args)
+      throws CompilationFailedException {
+    return parse(handler, prepareArgs(args));
+  }
+
+  /**
+   * Tests in this class are executed for all of D8, R8 and L8. When testing arguments this can add
+   * additional required arguments to all tests
+   */
+  abstract String[] requiredArgsForTest();
+
+  abstract C parse(String... args) throws CompilationFailedException;
+
+  abstract C parse(DiagnosticsHandler handler, String... args) throws CompilationFailedException;
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 5be6b85..9b80b80 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -43,7 +43,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class D8CommandTest extends TestBase {
+public class D8CommandTest extends CommandTestBase<D8Command> {
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -657,12 +657,18 @@
     numThreadsOptionInvalid("two");
   }
 
-  private D8Command parse(String... args) throws CompilationFailedException {
+  @Override
+  String[] requiredArgsForTest() {
+    return new String[0];
+  }
+
+  @Override
+  D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
 
-  private D8Command parse(DiagnosticsHandler handler, String... args)
-      throws CompilationFailedException {
+  @Override
+  D8Command parse(DiagnosticsHandler handler, String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE, handler).build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index b62416f..730d952 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -14,13 +14,16 @@
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
 
 // Helper to check that a particular error occurred.
 public class DiagnosticsChecker implements DiagnosticsHandler {
+
   public List<Diagnostic> errors = new ArrayList<>();
   public List<Diagnostic> warnings = new ArrayList<>();
   public List<Diagnostic> infos = new ArrayList<>();
@@ -41,6 +44,7 @@
   }
 
   public interface FailingRunner {
+
     void run(DiagnosticsHandler handler) throws CompilationFailedException;
   }
 
@@ -56,18 +60,38 @@
         diagnostics.stream().anyMatch(d -> d.getDiagnosticMessage().contains(snippet)));
   }
 
+  public void checkWarningsContains(String snippet) {
+    checkContains(snippet, warnings);
+  }
+
+  public void checkInfosContains(String snippet) {
+    checkContains(snippet, infos);
+  }
+
   public static void checkErrorsContains(String snippet, FailingRunner runner)
       throws CompilationFailedException {
+    checkErrorsContains(ImmutableList.of(snippet), runner);
+  }
+
+  public static void checkErrorsContains(Collection<String> snippets, FailingRunner runner)
+      throws CompilationFailedException {
     DiagnosticsChecker handler = new DiagnosticsChecker();
     try {
       runner.run(handler);
       fail("Failure expected");
     } catch (CompilationFailedException e) {
-      checkContains(snippet, handler.errors);
+      snippets.forEach(snippet -> checkContains(snippet, handler.errors));
       throw e;
     }
   }
 
+  public static void checkDiagnostics(Consumer<DiagnosticsChecker> checker, FailingRunner runner)
+      throws CompilationFailedException {
+    DiagnosticsChecker handler = new DiagnosticsChecker();
+    runner.run(handler);
+    checker.accept(handler);
+  }
+
   public static void checkWarningsContains(String snippet, FailingRunner runner)
       throws CompilationFailedException {
     DiagnosticsChecker handler = new DiagnosticsChecker();
@@ -75,6 +99,13 @@
     checkContains(snippet, handler.warnings);
   }
 
+  public static void checkInfosContains(String snippet, FailingRunner runner)
+      throws CompilationFailedException {
+    DiagnosticsChecker handler = new DiagnosticsChecker();
+    runner.run(handler);
+    checkContains(snippet, handler.infos);
+  }
+
   public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Consumer<Origin> originChecker,
       int lineStart, int columnStart, String... messageParts) {
     if (originChecker != null) {
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index c83cd38..978c382 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -36,7 +36,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class L8CommandTest extends TestBase {
+public class L8CommandTest extends CommandTestBase<L8Command> {
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -435,12 +435,18 @@
     numThreadsOptionInvalid("two");
   }
 
-  private L8Command parse(String... args) throws CompilationFailedException {
+  @Override
+  String[] requiredArgsForTest() {
+    return new String[] {"--desugared-lib", ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString()};
+  }
+
+  @Override
+  L8Command parse(String... args) throws CompilationFailedException {
     return L8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
 
-  private L8Command parse(DiagnosticsHandler handler, String... args)
-      throws CompilationFailedException {
+  @Override
+  L8Command parse(DiagnosticsHandler handler, String... args) throws CompilationFailedException {
     return L8Command.parse(args, EmbeddedOrigin.INSTANCE, handler).build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/MarkersTest.java b/src/test/java/com/android/tools/r8/MarkersTest.java
index 1fdc6ae..6e99dc4 100644
--- a/src/test/java/com/android/tools/r8/MarkersTest.java
+++ b/src/test/java/com/android/tools/r8/MarkersTest.java
@@ -87,18 +87,18 @@
             markerCompilationMode(compilationMode),
             markerDesugaredLibraryIdentifier("com.tools.android:desugar_jdk_libs:" + version),
             markerHasChecksums(false));
-    Matcher<Marker> d8Matcher =
+    Matcher<Marker> r8Matcher =
         allOf(
             markerTool(Tool.R8),
             markerCompilationMode(compilationMode),
             markerMinApi(apiLevel),
             markerR8Mode("compatibility"));
-    Matcher<Marker> r8Matcher =
+    Matcher<Marker> d8Matcher =
         allOf(markerTool(Tool.D8), markerCompilationMode(compilationMode), markerMinApi(apiLevel));
     if (shrinkDesugaredLibrary) {
-      assertMarkersMatch(markers, ImmutableList.of(l8Matcher, d8Matcher));
-    } else {
       assertMarkersMatch(markers, ImmutableList.of(l8Matcher, r8Matcher));
+    } else {
+      assertMarkersMatch(markers, ImmutableList.of(l8Matcher, d8Matcher));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 27d4655..622d595 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -27,6 +27,9 @@
     extends TestShrinkerBuilder<
         R8Command, Builder, ProguardTestCompileResult, ProguardTestRunResult, ProguardTestBuilder> {
 
+  // Version of Proguard to use.
+  private final ProguardVersion version;
+
   // Ordered list of injar entries.
   private List<Path> injars = new ArrayList<>();
 
@@ -39,12 +42,14 @@
   // Additional Proguard configuration files.
   private List<Path> proguardConfigFiles = new ArrayList<>();
 
-  private ProguardTestBuilder(TestState state, Builder builder, Backend backend) {
+  private ProguardTestBuilder(
+      TestState state, ProguardVersion version, Builder builder, Backend backend) {
     super(state, builder, backend);
+    this.version = version;
   }
 
-  public static ProguardTestBuilder create(TestState state) {
-    return new ProguardTestBuilder(state, R8Command.builder(), Backend.CF);
+  public static ProguardTestBuilder create(TestState state, ProguardVersion version) {
+    return new ProguardTestBuilder(state, version, R8Command.builder(), Backend.CF);
   }
 
   @Override
@@ -63,7 +68,7 @@
       Path mapFile = proguardOutputFolder.resolve("mapping.txt");
       FileUtils.writeTextFile(configFile, config);
       List<String> command = new ArrayList<>();
-      command.add(ToolHelper.getProguard6Script());
+      command.add(version.getProguardScript().toString());
       // Without -forceprocessing Proguard just checks the creation time on the in/out jars.
       command.add("-forceprocessing");
       for (Path injar : injars) {
diff --git a/src/test/java/com/android/tools/r8/ProguardVersion.java b/src/test/java/com/android/tools/r8/ProguardVersion.java
new file mode 100644
index 0000000..8c9050a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ProguardVersion.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import static com.android.tools.r8.ToolHelper.isWindows;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public enum ProguardVersion {
+  V5_2_1("5.2.1"),
+  V6_0_1("6.0.1"),
+  V7_0_0("7.0.0");
+
+  private final String version;
+
+  ProguardVersion(String version) {
+    this.version = version;
+  }
+
+  public Path getProguardScript() {
+    Path scriptDirectory = Paths.get(ToolHelper.THIRD_PARTY_DIR).resolve("proguard");
+    if (this == V7_0_0) {
+      scriptDirectory = scriptDirectory.resolve("proguard-" + version);
+    } else {
+      scriptDirectory = scriptDirectory.resolve("proguard" + version);
+    }
+    if (isWindows()) {
+      return scriptDirectory.resolve("bin/proguard.bat");
+    }
+    return scriptDirectory.resolve("bin/proguard.sh");
+  }
+
+  @Override
+  public String toString() {
+    return "Proguard " + version.toString();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 149c3c3..2f94aeb 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -45,7 +45,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class R8CommandTest extends TestBase {
+public class R8CommandTest extends CommandTestBase<R8Command> {
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -820,12 +820,18 @@
     numThreadsOptionInvalid("two");
   }
 
-  private R8Command parse(String... args) throws CompilationFailedException {
+  @Override
+  String[] requiredArgsForTest() {
+    return new String[0];
+  }
+
+  @Override
+  R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
 
-  private R8Command parse(DiagnosticsHandler handler, String... args)
-      throws CompilationFailedException {
+  @Override
+  R8Command parse(DiagnosticsHandler handler, String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE, handler).build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a92fefd..27c1570 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -172,8 +172,8 @@
     return JvmTestBuilder.create(new TestState(temp));
   }
 
-  public static ProguardTestBuilder testForProguard(TemporaryFolder temp) {
-    return ProguardTestBuilder.create(new TestState(temp));
+  public static ProguardTestBuilder testForProguard(ProguardVersion version, TemporaryFolder temp) {
+    return ProguardTestBuilder.create(new TestState(temp), version);
   }
 
   public static GenerateMainDexListTestBuilder testForMainDexListGenerator(TemporaryFolder temp) {
@@ -308,8 +308,14 @@
     return DesugarTestBuilder.create(state, builders.build());
   }
 
+  /** @deprecated use {@link #testForProguard(ProguardVersion)} instead. */
+  @Deprecated
   public ProguardTestBuilder testForProguard() {
-    return testForProguard(temp);
+    return testForProguard(ProguardVersion.V6_0_1);
+  }
+
+  public ProguardTestBuilder testForProguard(ProguardVersion version) {
+    return testForProguard(version, temp);
   }
 
   public GenerateMainDexListTestBuilder testForMainDexListGenerator() {
diff --git a/src/test/java/com/android/tools/r8/TestDiagnostic.java b/src/test/java/com/android/tools/r8/TestDiagnostic.java
new file mode 100644
index 0000000..b5df861
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestDiagnostic.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+class TestDiagnostic implements Diagnostic {
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return null;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "Test diagnostic";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 477aab1..5f674e0 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -94,6 +94,10 @@
     return addKeepRules(Arrays.asList(rules));
   }
 
+  public T addDontWarn(Class<?> clazz) {
+    return addKeepRules("-dontwarn " + clazz.getTypeName());
+  }
+
   public T addKeepKotlinMetadata() {
     return addKeepRules("-keep class kotlin.Metadata { *; }");
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 7e1a627..9676011 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -702,7 +702,7 @@
     }
   }
 
-  public static String getProguardScript() {
+  public static String getProguard5Script() {
     if (isWindows()) {
       return PROGUARD + ".bat";
     }
@@ -1922,17 +1922,17 @@
 
   public static ProcessResult runProguardRaw(
       Path inJar, Path outJar, Path lib, Path config, Path map) throws IOException {
-    return runProguardRaw(getProguardScript(), inJar, outJar, lib, ImmutableList.of(config), map);
+    return runProguardRaw(getProguard5Script(), inJar, outJar, lib, ImmutableList.of(config), map);
   }
 
   public static ProcessResult runProguardRaw(Path inJar, Path outJar, List<Path> config, Path map)
       throws IOException {
-    return runProguardRaw(getProguardScript(), inJar, outJar, config, map);
+    return runProguardRaw(getProguard5Script(), inJar, outJar, config, map);
   }
 
   public static ProcessResult runProguardRaw(Path inJar, Path outJar, Path config, Path map)
       throws IOException {
-    return runProguardRaw(getProguardScript(), inJar, outJar, ImmutableList.of(config), map);
+    return runProguardRaw(getProguard5Script(), inJar, outJar, ImmutableList.of(config), map);
   }
 
   public static String runProguard(Path inJar, Path outJar, Path config, Path map)
@@ -1942,7 +1942,7 @@
 
   public static String runProguard(Path inJar, Path outJar, List<Path> config, Path map)
       throws IOException {
-    return runProguard(getProguardScript(), inJar, outJar, config, map);
+    return runProguard(getProguard5Script(), inJar, outJar, config, map);
   }
 
   public static ProcessResult runProguard6Raw(Path inJar, Path outJar, Path config, Path map)
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
index 982fb65..4a81ae5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
@@ -4,17 +4,14 @@
 
 package com.android.tools.r8.classmerging.vertical;
 
-import static org.hamcrest.CoreMatchers.containsString;
+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.Assert.assertTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.errors.InternalCompilerError;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.Arrays;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,21 +34,21 @@
 
   @Test
   public void test() throws Exception {
-    try {
-      testForR8(parameters.getBackend())
-          .addInnerClasses(getClass())
-          .addKeepMainRule(TestClass.class)
-          .enableCoreLibraryDesugaring(parameters.getApiLevel())
-          .enableNeverClassInliningAnnotations()
-          .setMinApi(parameters.getApiLevel())
-          .compile();
-      assertTrue(parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
-    } catch (CompilationFailedException e) {
-      // TODO(b/170677722): Fix compilation failure.
-      assertTrue(parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
-      assertTrue(e.getCause() instanceof InternalCompilerError);
-      assertThat(e.getCause().getMessage(), containsString("FORCE inlining on non-inlinable"));
-    }
+    // Regression test for b/170677722.
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(A.class))
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), not(isPresent()));
+              assertThat(inspector.clazz(B.class), isPresent());
+            });
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
index a01a59a..9b48952 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
@@ -7,7 +7,6 @@
 import static org.junit.Assert.assertEquals;
 
 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 java.nio.file.Path;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
index ac48a3d..0707ab9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -4,11 +4,13 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Spliterator;
@@ -55,6 +57,49 @@
   }
 
   @Test
+  public void testCustomCollectionSuperCallsD8Cf2Cf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(CustomCollectionSuperCallsTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .allowStdoutMessages()
+            .compile()
+            .writeToZip();
+    String desugaredLibraryKeepRules = "";
+    if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
+      // Collection keep rules is only implemented in the DEX writer.
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class * { *; }";
+    }
+    D8TestRunResult d8TestRunResult;
+    if (parameters.getRuntime().isDex()) {
+      d8TestRunResult =
+          testForD8()
+              .addProgramFiles(jar)
+              .setMinApi(parameters.getApiLevel())
+              .disableDesugaring()
+              .allowStdoutMessages()
+              .compile()
+              .addDesugaredCoreLibraryRunClassPath(
+                  this::buildDesugaredLibrary,
+                  parameters.getApiLevel(),
+                  desugaredLibraryKeepRules,
+                  shrinkDesugaredLibrary)
+              .run(parameters.getRuntime(), Executor.class)
+              .assertSuccess();
+      assertLines2By2Correct(d8TestRunResult.getStdOut());
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccess();
+    }
+  }
+
+  @Test
   public void testCustomCollectionSuperCallsR8() throws Exception {
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     R8TestRunResult r8TestRunResult =
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 3df7246..0947a55 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestRunResult;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -70,6 +72,56 @@
     assertResultCorrect(d8TestRunResult.getStdOut());
   }
 
+  @Test
+  public void testCustomCollectionD8Cf2Cf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(CustomCollectionTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .writeToZip();
+    if (parameters.getRuntime().isDex()) {
+      // Collection keep rules is only implemented in the DEX writer.
+      String desugaredLibraryKeepRules = keepRuleConsumer.get();
+      if (desugaredLibraryKeepRules != null) {
+        assertEquals(0, desugaredLibraryKeepRules.length());
+        desugaredLibraryKeepRules = "-keep class * { *; }";
+      }
+      D8TestRunResult d8TestRunResult =
+          testForD8()
+              .addProgramFiles(jar)
+              .setMinApi(parameters.getApiLevel())
+              .disableDesugaring()
+              .compile()
+              .assertNoMessages()
+              .inspect(
+                  inspector -> {
+                    this.assertCustomCollectionCallsCorrect(inspector);
+                  })
+              .addDesugaredCoreLibraryRunClassPath(
+                  this::buildDesugaredLibrary,
+                  parameters.getApiLevel(),
+                  desugaredLibraryKeepRules,
+                  shrinkDesugaredLibrary)
+              .run(parameters.getRuntime(), EXECUTOR)
+              .assertSuccess();
+      assertResultCorrect(d8TestRunResult.getStdOut());
+    } else {
+      // Build the desugared library in class file format.
+      Path desugaredLib = getDesugaredLibraryInCF(parameters, o -> {});
+
+      // Run on the JVM with desuagred library on classpath.
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(desugaredLib)
+          .run(parameters.getRuntime(), EXECUTOR)
+          .assertSuccess();
+    }
+  }
+
   private void assertResultCorrect(String stdOut) {
     if (requiresEmulatedInterfaceCoreLibDesugaring(parameters) && !shrinkDesugaredLibrary) {
       // When shrinking the class names are not printed correctly anymore due to minification.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
index ff0141f..a3b768d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
@@ -11,13 +11,14 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-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 java.nio.file.Path;
 import java.time.LocalDate;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -33,7 +34,8 @@
   @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
   }
 
   public DesugaredGenericSignatureTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
@@ -43,6 +45,7 @@
 
   @Test
   public void testD8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(DesugaredGenericSignatureTest.class)
@@ -61,7 +64,51 @@
   }
 
   @Test
+  public void testD8Cf2Cf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(DesugaredGenericSignatureTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .setIncludeClassesChecksum(true)
+            .setMinApi(parameters.getApiLevel())
+            .allowStdoutMessages()
+            .compile()
+            .writeToZip();
+    String desugaredLibraryKeepRules = "";
+    if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
+      // Collection keep rules is only implemented in the DEX writer.
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class * { *; }";
+    }
+    if (parameters.getRuntime().isDex()) {
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .allowStdoutMessages()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary,
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED);
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED);
+    }
+  }
+
+  @Test
   public void testR8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .addInnerClasses(DesugaredGenericSignatureTest.class)
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 e4b3869..b02b2e9 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
@@ -119,7 +119,6 @@
           options -> {
             if (extraFiles) {
               options.testing.disableL8AnnotationRemoval = true;
-              options.testing.forceLibBackportsInL8CfToCf = true;
             }
             optionsModifier.accept(options);
           });
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index 346743b..b726614 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -93,32 +93,28 @@
             .parse(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
 
     for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
-      if (apiLevel == AndroidApiLevel.R) {
-        // Skip API level 30 for now.
+      Path compileApiLevelDirectory = directory.resolve("compile_api_level_" + apiLevel.getLevel());
+      if (apiLevel.getLevel()
+          < desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel()) {
+        System.out.println("!Checking " + compileApiLevelDirectory);
         continue;
       }
-
-      if (apiLevel.getLevel()
-          >= desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel()) {
-        Path compileApiLevelDirectory =
-            directory.resolve("compile_api_level_" + apiLevel.getLevel());
-        assertTrue(Files.exists(compileApiLevelDirectory));
-        for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
-          String desugaredApisBaseName =
-              "desugared_apis_" + apiLevel.getLevel() + "_" + minApiLevel.getLevel();
-          if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
-            assertTrue(
-                Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
-            assertTrue(
-                Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
-            checkFileContent(
-                minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
-          } else {
-            assertFalse(
-                Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
-            assertFalse(
-                Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
-          }
+      assertTrue(Files.exists(compileApiLevelDirectory));
+      for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
+        String desugaredApisBaseName =
+            "desugared_apis_" + apiLevel.getLevel() + "_" + minApiLevel.getLevel();
+        if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
+          assertTrue(
+              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+          assertTrue(
+              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
+          checkFileContent(
+              minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
+        } else {
+          assertFalse(
+              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+          assertFalse(
+              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java
index c663489..c9b2a93 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java
@@ -4,10 +4,12 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static org.junit.Assert.assertEquals;
+
 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 java.nio.file.Path;
 import java.time.chrono.Chronology;
 import java.util.List;
 import java.util.Map;
@@ -28,7 +30,8 @@
   @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
   }
 
   public StaticInterfaceMethodTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
@@ -61,6 +64,45 @@
   }
 
   @Test
+  public void testStaticInterfaceMethodsD8CfToCf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(StaticInterfaceMethodTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .writeToZip();
+
+    String desugaredLibraryKeepRules = "";
+    if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
+      // Collection keep rules is only implemented in the DEX writer.
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class * { *; }";
+    }
+    if (parameters.getRuntime().isDex()) {
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary,
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    }
+  }
+
+  @Test
   public void testStaticInterfaceMethodsR8() throws Exception {
     // Desugared library tests do not make sense in the Cf to Cf, and the JVM is already tested
     // in the D8 test. Just return.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index e8aa581..6afc3a5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -80,6 +82,52 @@
   }
 
   @Test
+  public void testFunctionCompositionD8Cf2Cf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path jar =
+        testForD8(Backend.CF)
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClasses(
+                Executor.class,
+                Executor.Object1.class,
+                Executor.Object2.class,
+                Executor.Object3.class)
+            .addLibraryClasses(CustomLibClass.class)
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .writeToZip();
+    String desugaredLibraryKeepRules = "";
+    if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
+      // Collection keep rules is only implemented in the DEX writer.
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class * { *; }";
+    }
+    if (parameters.getRuntime().isDex()) {
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary,
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .addRunClasspathFiles(CUSTOM_LIB)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
+          .addRunClasspathFiles(CUSTOM_LIB)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    }
+  }
+
+  @Test
   public void testFunctionCompositionR8() throws Exception {
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
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 9d9b5cb..394f36c 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
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -37,23 +38,27 @@
 @RunWith(Parameterized.class)
 public class Jdk11StreamTests extends Jdk11DesugaredLibraryTestBase {
 
+  private final boolean useCf2Cf;
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "{2}, shrinkDesugaredLibrary: {0}, useCf2Cf: {1}")
   public static List<Object[]> data() {
     // TODO(134732760): Support Dalvik VMs, currently fails because libjavacrypto is required and
     // present only in ART runtimes.
     return buildParameters(
         BooleanUtils.values(),
+        BooleanUtils.values(),
         getTestParameters()
             .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
             .withAllApiLevels()
             .build());
   }
 
-  public Jdk11StreamTests(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+  public Jdk11StreamTests(
+      boolean shrinkDesugaredLibrary, boolean useCf2Cf, TestParameters parameters) {
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.useCf2Cf = useCf2Cf;
     this.parameters = parameters;
   }
 
@@ -188,34 +193,71 @@
         "Requires Java base extensions, should add it when not desugaring",
         parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel());
 
-    D8TestCompileResult compileResult = compileStreamTestsToDex();
+    D8TestCompileResult compileResult = compileStreamTestsToDex(useCf2Cf);
     runSuccessfulTests(compileResult);
     runFailingTests(compileResult);
   }
 
-  private D8TestCompileResult compileStreamTestsToDex() throws Exception {
+  private D8TestCompileResult compileStreamTestsToDex(boolean cf2cf) throws Exception {
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     List<Path> filesToCompile =
         Arrays.stream(JDK_11_STREAM_TEST_COMPILED_FILES)
             .filter(file -> !file.toString().contains("lang/invoke"))
             .collect(Collectors.toList());
-    return testForD8()
-        .addProgramFiles(filesToCompile)
-        .addProgramFiles(getPathsFiles())
-        .addProgramFiles(getSafeVarArgsFile())
-        .addProgramFiles(testNGSupportProgramFiles())
-        .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibraryWithJavaBaseExtension,
-            parameters.getApiLevel(),
-            keepRuleConsumer.get(),
-            shrinkDesugaredLibrary)
-        .withArtFrameworks()
-        .withArt6Plus64BitsLib();
+
+    if (cf2cf) {
+      Path jar =
+          testForD8(Backend.CF)
+              .addProgramFiles(filesToCompile)
+              .addProgramFiles(getPathsFiles())
+              .addProgramFiles(getSafeVarArgsFile())
+              .addProgramFiles(testNGSupportProgramFiles())
+              .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+              .setMinApi(parameters.getApiLevel())
+              .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+              .allowStdoutMessages()
+              .compile()
+              .writeToZip();
+      // Collection keep rules is only implemented in the DEX writer.
+      String desugaredLibraryKeepRules = keepRuleConsumer.get();
+      if (desugaredLibraryKeepRules != null) {
+        assertEquals(0, desugaredLibraryKeepRules.length());
+        desugaredLibraryKeepRules = "-keep class * { *; }";
+      }
+      return testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibraryWithJavaBaseExtension,
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .withArtFrameworks()
+          .withArt6Plus64BitsLib();
+
+    } else {
+
+      return testForD8()
+          .addProgramFiles(filesToCompile)
+          .addProgramFiles(getPathsFiles())
+          .addProgramFiles(getSafeVarArgsFile())
+          .addProgramFiles(testNGSupportProgramFiles())
+          .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+          .setMinApi(parameters.getApiLevel())
+          .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibraryWithJavaBaseExtension,
+              parameters.getApiLevel(),
+              keepRuleConsumer.get(),
+              shrinkDesugaredLibrary)
+          .withArtFrameworks()
+          .withArt6Plus64BitsLib();
+    }
   }
 
   private void runSuccessfulTests(D8TestCompileResult compileResult) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java
new file mode 100644
index 0000000..7f5b905
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/SingletonCanonicalizationWithApiLevelCheckTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.canonicalization;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableList;
+import java.io.PrintStream;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SingletonCanonicalizationWithApiLevelCheckTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SingletonCanonicalizationWithApiLevelCheckTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path program =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(TestClass.class, Companion.class, A.class)
+            .addLibraryClasses(LibraryVersion.class, LibraryInterfaceAddedInApi1.class)
+            .addDefaultRuntimeLibrary(parameters)
+            .addKeepMainRule(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+
+    // Run with library version 1 that includes LibraryInterfaceAddedInApi1.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(LibraryVersion.class, LibraryInterfaceAddedInApi1.class)
+        .addKeepAllClassesRule()
+        .addKeepRules(getAssumeValuesRule(1))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(program)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("A");
+
+    // Run with library version 0 that does not include LibraryInterfaceAddedInApi1.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(LibraryVersion.class)
+        .addKeepAllClassesRule()
+        .addKeepRules(getAssumeValuesRule(0))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(program)
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(
+            runResult -> {
+              if (parameters.isCfRuntime()) {
+                // Constant canonicalization is disabled for CF.
+                runResult.assertSuccessWithEmptyOutput();
+              } else {
+                runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+              }
+            });
+  }
+
+  private List<String> getAssumeValuesRule(int version) {
+    return ImmutableList.of(
+        "-assumevalues class " + LibraryVersion.class.getTypeName() + " {",
+        "  static int init() return " + version + ";",
+        "}");
+  }
+
+  public static class LibraryVersion {
+
+    public static int SDK_INT = init();
+
+    private static int init() {
+      return 0;
+    }
+  }
+
+  public interface LibraryInterfaceAddedInApi1 {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (LibraryVersion.SDK_INT >= 1) {
+        PrintStream out = System.out;
+        if (System.currentTimeMillis() > 0) {
+          out.println(Companion.INSTANCE);
+        } else {
+          out.print(Companion.INSTANCE);
+        }
+      }
+    }
+  }
+
+  static class Companion {
+
+    static A INSTANCE = new A();
+  }
+
+  static class A implements LibraryInterfaceAddedInApi1 {
+
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
index 95fef57..415a868 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
@@ -5,13 +5,12 @@
 package com.android.tools.r8.ir.optimize.inliner;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.Arrays;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,14 +40,10 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
-            inspector -> {
-              // TODO(b/171197204): Method should be inlined.
-              assertThat(
-                  inspector.clazz(TestClass.class).uniqueMethodWithName("test"),
-                  notIf(
-                      isPresent(),
-                      parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)));
-            });
+            inspector ->
+                assertThat(
+                    inspector.clazz(TestClass.class).uniqueMethodWithName("test"),
+                    not(isPresent())));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingTest.java
new file mode 100644
index 0000000..ed90756
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingTest.java
@@ -0,0 +1,123 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.memberrebinding;
+
+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.nio.file.Path;
+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 LibraryMemberRebindingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LibraryMemberRebindingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testWithEmptyA() throws Exception {
+    Path compileTimeLibrary = compileLibraryForAppCompilation(LibraryB.class, LibraryA.class);
+    test(compileTimeLibrary, compileTimeLibrary);
+  }
+
+  @Test
+  public void testWithEmptyB() throws Exception {
+    Path compileTimeLibrary = compileLibraryForAppCompilation(LibraryA.class, LibraryB.class);
+    test(compileTimeLibrary, compileTimeLibrary);
+  }
+
+  @Test
+  public void testWithEmptyBOnlyAtCompileTime() throws Exception {
+    Path compileTimeLibrary = compileLibraryForAppCompilation(LibraryA.class, LibraryB.class);
+    Path runtimeLibrary = compileLibraryForAppCompilation(LibraryB.class, LibraryA.class);
+    test(compileTimeLibrary, runtimeLibrary);
+  }
+
+  private void test(Path compileTimeLibrary, Path runtimeLibrary) throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addLibraryFiles(compileTimeLibrary)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .apply(compileResult -> configureRunClasspath(compileResult, runtimeLibrary))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  private Path compileLibraryForAppCompilation(
+      Class<?> nonEmptyLibraryClass, Class<?> emptyLibraryClass) throws Exception {
+    return testForR8(Backend.CF)
+        .addProgramClasses(nonEmptyLibraryClass)
+        .addProgramClassFileData(
+            transformer(emptyLibraryClass)
+                .removeFields(
+                    (int access, String name, String descriptor, String signature, Object value) ->
+                        true)
+                .removeMethods(
+                    (int access,
+                        String name,
+                        String descriptor,
+                        String signature,
+                        String[] exceptions) -> !name.equals("<init>"))
+                .transform())
+        .addKeepAllClassesRule()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .writeToZip();
+  }
+
+  private void configureRunClasspath(R8TestCompileResult compileResult, Path library)
+      throws Exception {
+    if (parameters.isCfRuntime()) {
+      compileResult.addRunClasspathFiles(library);
+    } else {
+      compileResult.addRunClasspathFiles(
+          testForD8()
+              .addProgramFiles(library)
+              .setMinApi(parameters.getApiLevel())
+              .compile()
+              .writeToZip());
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(LibraryB.f + LibraryB.m());
+    }
+  }
+
+  static class LibraryA {
+
+    public static int f = 21;
+
+    public static int m() {
+      return 21;
+    }
+  }
+
+  static class LibraryB extends LibraryA {
+
+    public static int f = 21;
+
+    public static int m() {
+      return 21;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java
new file mode 100644
index 0000000..ca30a1f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.proguard;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 AllowShrinkingCompatibilityTest extends TestBase {
+
+  private final boolean allowOptimization;
+  private final TestParameters parameters;
+  private final ProguardVersion proguardVersion;
+
+  @Parameters(name = "{1}, {2}, allow optimization: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters().withCfRuntimes().build(),
+        ProguardVersion.values());
+  }
+
+  public AllowShrinkingCompatibilityTest(
+      boolean allowOptimization, TestParameters parameters, ProguardVersion proguardVersion) {
+    this.allowOptimization = allowOptimization;
+    this.parameters = parameters;
+    this.proguardVersion = proguardVersion;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForProguard(proguardVersion)
+        .addProgramClasses(TestClass.class, Companion.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-keep,allowshrinking"
+                + (allowOptimization ? ",allowoptimization" : "")
+                + " class "
+                + Companion.class.getTypeName()
+                + " { <methods>; }")
+        .addDontWarn(getClass())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+              assertThat(testClassSubject, isPresent());
+
+              ClassSubject companionClassSubject = inspector.clazz(Companion.class);
+              assertThat(companionClassSubject, notIf(isPresent(), allowOptimization));
+
+              MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+              MethodSubject getMethodSubject = companionClassSubject.uniqueMethodWithName("get");
+
+              if (allowOptimization) {
+                assertTrue(
+                    testClassSubject
+                        .mainMethod()
+                        .streamInstructions()
+                        .allMatch(InstructionSubject::isReturnVoid));
+              } else {
+                assertThat(mainMethodSubject, invokesMethod(getMethodSubject));
+                assertThat(getMethodSubject, isPresent());
+              }
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (Companion.get() != 42) {
+        System.out.println("Hello world!");
+      }
+    }
+  }
+
+  static class Companion {
+
+    static int get() {
+      return 42;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java
index 3a6ffe6..2b90ffc 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.mappings.FieldsWithSameMinifiedNameMapping;
 import com.android.tools.r8.retrace.mappings.MappingForTest;
 import java.util.function.Consumer;
@@ -13,13 +14,13 @@
 public class RetraceFieldTests {
 
   @Test
-  public void testFieldsWithSameMinifiedName() throws Exception {
+  public void testFieldsWithSameMinifiedName() {
     FieldsWithSameMinifiedNameMapping mapping = new FieldsWithSameMinifiedNameMapping();
     runRetraceTest(mapping, mapping::inspect);
   }
 
-  private void runRetraceTest(MappingForTest mappingForTest, Consumer<RetraceApi> inspection)
-      throws Exception {
-    inspection.accept(Retracer.create(mappingForTest::mapping, new TestDiagnosticMessagesImpl()));
+  private void runRetraceTest(MappingForTest mappingForTest, Consumer<Retracer> inspection) {
+    inspection.accept(
+        RetracerImpl.create(mappingForTest::mapping, new TestDiagnosticMessagesImpl()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
index 4fb8389..b8b1647 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
@@ -440,7 +440,7 @@
 
           @Override
           public List<String> retracedStackTrace() {
-            return ImmutableList.of("com.android.tools.r8.R8.a(42)");
+            return ImmutableList.of("com.android.tools.r8.R8.foo(42)");
           }
 
           @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 1b6735f..edcb118 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -15,7 +15,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.retrace.Retrace.RetraceAbortException;
+import com.android.tools.r8.retrace.internal.RetraceAbortException;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.stacktraces.ActualBotStackTraceBase;
 import com.android.tools.r8.retrace.stacktraces.ActualIdentityStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
@@ -26,6 +27,7 @@
 import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineFileNameWithInnerClassesStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineSourceFileContextStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
@@ -85,6 +87,11 @@
   }
 
   @Test
+  public void testInlineFileNameWithInnerClassesStackTrace() {
+    runRetraceTest(new InlineFileNameWithInnerClassesStackTrace());
+  }
+
+  @Test
   public void testNullLineTrace() {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     NullStackTrace nullStackTrace = new NullStackTrace();
@@ -201,9 +208,9 @@
   }
 
   private void inspectRetraceTest(
-      StackTraceForTest stackTraceForTest, Consumer<RetraceApi> inspection) throws Exception {
+      StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) throws Exception {
     inspection.accept(
-        Retracer.create(stackTraceForTest::mapping, new TestDiagnosticMessagesImpl()));
+        RetracerImpl.create(stackTraceForTest::mapping, new TestDiagnosticMessagesImpl()));
   }
 
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
diff --git a/src/test/java/com/android/tools/r8/retrace/mappings/FieldsWithSameMinifiedNameMapping.java b/src/test/java/com/android/tools/r8/retrace/mappings/FieldsWithSameMinifiedNameMapping.java
index 488a00a..fa9ca5f 100644
--- a/src/test/java/com/android/tools/r8/retrace/mappings/FieldsWithSameMinifiedNameMapping.java
+++ b/src/test/java/com/android/tools/r8/retrace/mappings/FieldsWithSameMinifiedNameMapping.java
@@ -9,8 +9,8 @@
 
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.RetraceApi;
 import com.android.tools.r8.retrace.RetraceFieldResult;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -25,7 +25,7 @@
         "foo.bar.Baz -> foo.bar.Baz:", "  java.lang.Object f1 -> a", "  java.lang.String f2 -> a");
   }
 
-  public void inspect(RetraceApi retracer) {
+  public void inspect(Retracer retracer) {
     FieldReference f1FieldReference =
         Reference.field(
             Reference.classFromTypeName("foo.bar.Baz"),
@@ -43,7 +43,7 @@
             "a",
             Reference.classFromTypeName("java.lang.Object"));
 
-    RetraceFieldResult result = retracer.retrace(mappedF1FieldReference);
+    RetraceFieldResult result = retracer.retraceField(mappedF1FieldReference);
     // TODO(b/169829306): Result should not be ambigious.
     assertTrue(result.isAmbiguous());
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java
new file mode 100644
index 0000000..20ed6ec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineFileNameWithInnerClassesStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:3)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat foo.Bar$Baz$Qux.baz(Bar.dummy:0)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:7)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> com.android.tools.r8.naming.retrace.Main:",
+        "    3:3:void foo.Bar$Baz$Qux.baz(long):0:0 -> main",
+        "    3:3:void main(java.lang.String[]):7 -> main");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java
index 3ca3297..080990f 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java
@@ -9,9 +9,9 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.RetraceApi;
 import com.android.tools.r8.retrace.RetraceFieldResult;
 import com.android.tools.r8.retrace.RetraceFieldResult.Element;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
 import java.util.List;
@@ -42,12 +42,12 @@
     return 1;
   }
 
-  public void inspectField(RetraceApi retracer) {
+  public void inspectField(Retracer retracer) {
     RetraceFieldResult result =
-        retracer.retrace(Reference.classFromTypeName("a.A")).lookupField("a");
+        retracer.retraceClass(Reference.classFromTypeName("a.A")).lookupField("a");
     assertFalse(result.isAmbiguous());
     assertEquals(1, result.stream().count());
-    Optional<Element> field = result.stream().findFirst();
+    Optional<? extends Element> field = result.stream().findFirst();
     assertTrue(field.isPresent());
     assertEquals("field", field.get().getField().getFieldName());
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 222c6a8..c15fb48 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -3175,4 +3175,33 @@
           }
         });
   }
+
+  @Test
+  public void parseKeepXNamesIsAllowShrinking() {
+    for (String rule : ImmutableList.of("-keep", "-keepclassmembers", "-keepclasseswithmembers")) {
+      for (String modifier :
+          ImmutableList.of("", "names", ",allowshrinking", "names,allowshrinking")) {
+        DexItemFactory dexItemFactory = new DexItemFactory();
+        ProguardConfigurationParser parser =
+            new ProguardConfigurationParser(dexItemFactory, reporter);
+        String ruleWithModifier =
+            (rule.endsWith("s") && (!modifier.startsWith(",") && !modifier.isEmpty())
+                    ? rule.substring(0, rule.length() - 1)
+                    : rule)
+                + modifier;
+        parser.parse(
+            createConfigurationForTesting(ImmutableList.of(ruleWithModifier + " class A")));
+        verifyParserEndsCleanly();
+
+        ProguardConfiguration configuration = parser.getConfig();
+        assertEquals(1, configuration.getRules().size());
+        assertEquals(
+            !modifier.isEmpty(),
+            ListUtils.first(configuration.getRules())
+                .asProguardKeepRule()
+                .getModifiers()
+                .allowsShrinking);
+      }
+    }
+  }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
new file mode 100644
index 0000000..cc1f241
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.attributes;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeepEnclosingMethodForKeptMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String[] EXPECTED = {
+    "null",
+    "class com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest"
+        + "$KeptClass",
+    "public static com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest$I "
+        + "com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest$KeptClass.enclosingFromKeptMethod()",
+    "public"
+        + " com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest$KeptClass()"
+  };
+  private final String[] R8_OUTPUT = {
+    "null",
+    "class com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest"
+        + "$KeptClass",
+    "null",
+    "null"
+  };
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepEnclosingMethodForKeptMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(KeptClass.class)
+        .addProgramClassFileData(
+            transformer(I.class).removeInnerClasses().transform(),
+            transformer(KeptClass.class).removeInnerClasses().transform())
+        .run(parameters.getRuntime(), KeptClass.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8Full() throws Exception {
+    // TODO(b/171194649): This should output EXPECTED.
+    runTest(testForR8(parameters.getBackend())).assertSuccessWithOutputLines(R8_OUTPUT);
+  }
+
+  @Test
+  public void testR8Compat() throws Exception {
+    runTest(testForR8Compat(parameters.getBackend())).assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private R8TestRunResult runTest(R8TestBuilder<?> testBuilder) throws Exception {
+    return testBuilder
+        .addInnerClasses(KeptClass.class)
+        .addProgramClassFileData(
+            transformer(I.class).removeInnerClasses().transform(),
+            transformer(KeptClass.class).removeInnerClasses().transform())
+        .addKeepClassRules(I.class)
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .addKeepMainRule(KeptClass.class)
+        .addKeepClassAndMembersRules(KeptClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), KeptClass.class);
+  }
+
+  public interface I {
+
+    void foo();
+  }
+
+  public static class KeptClass {
+
+    static I staticField =
+        new I() {
+          @Override
+          public void foo() {
+            System.out.println(this.getClass().getEnclosingConstructor());
+            System.out.println(this.getClass().getEnclosingClass());
+          }
+        };
+
+    private I instanceField;
+
+    public KeptClass() {
+      instanceField =
+          new I() {
+            @Override
+            public void foo() {
+              System.out.println(this.getClass().getEnclosingConstructor());
+            }
+          };
+    }
+
+    public static void main(String[] args) {
+      staticField.foo();
+      enclosingFromKeptMethod().foo();
+      new KeptClass().instanceField.foo();
+    }
+
+    @NeverInline
+    public static I enclosingFromKeptMethod() {
+      return new I() {
+        @Override
+        public void foo() {
+          System.out.println(this.getClass().getEnclosingMethod());
+        }
+      };
+    }
+  }
+}
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 1772e48..d2518b6 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
@@ -34,9 +34,9 @@
 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.DirectClassNameMapperProguardMapProducer;
-import com.android.tools.r8.retrace.RetraceApi;
 import com.android.tools.r8.retrace.Retracer;
+import com.android.tools.r8.retrace.internal.DirectClassNameMapperProguardMapProducer;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -463,8 +463,8 @@
     }
   }
 
-  public RetraceApi retrace() {
-    return Retracer.create(
+  public Retracer retrace() {
+    return RetracerImpl.create(
         new InternalProguardMapProducer(
             mapping == null ? ClassNameMapper.builder().build() : mapping),
         new TestDiagnosticMessagesImpl());
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 bcedc29..d8aa792 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
@@ -31,9 +31,9 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.RetraceApi;
 import com.android.tools.r8.retrace.RetraceTypeResult;
 import com.android.tools.r8.retrace.RetracedField;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
@@ -223,7 +223,7 @@
 
   // TODO(b/169882658): This should be removed when we have identity mappings for ambiguous cases.
   public FieldSubject uniqueFieldWithName(String name, TypeReference originalType) {
-    RetraceApi retracer = codeInspector.retrace();
+    Retracer retracer = codeInspector.retrace();
     Set<FoundFieldSubject> candidates = Sets.newIdentityHashSet();
     Set<FoundFieldSubject> sameTypeCandidates = Sets.newIdentityHashSet();
     for (FoundFieldSubject candidate : allFields()) {
@@ -236,7 +236,7 @@
         }
       }
       retracer
-          .retrace(fieldReference)
+          .retraceField(fieldReference)
           .forEach(
               element -> {
                 RetracedField field = element.getField();
@@ -246,7 +246,7 @@
                   TypeReference fieldOriginalType = originalType;
                   if (fieldOriginalType == null) {
                     RetraceTypeResult retraceTypeResult =
-                        retracer.retrace(fieldReference.getFieldType());
+                        retracer.retraceType(fieldReference.getFieldType());
                     assert !retraceTypeResult.isAmbiguous();
                     fieldOriginalType =
                         retraceTypeResult.stream().iterator().next().getType().getTypeReference();
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 6f6bbf8..e6e26fd 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,9 +6,9 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.retrace.RetraceApi;
 import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.Retracer;
 
 public interface InstructionSubject {
 
@@ -132,17 +132,17 @@
     return lineNumberTable == null ? -1 : lineNumberTable.getLineForInstruction(this);
   }
 
-  default RetraceMethodResult retrace(RetraceApi retracer) {
+  default RetraceMethodResult retrace(Retracer retracer) {
     MethodSubject methodSubject = getMethodSubject();
     assert methodSubject.isPresent();
-    return retracer.retrace(methodSubject.asFoundMethodSubject().asMethodReference());
+    return retracer.retraceMethod(methodSubject.asFoundMethodSubject().asMethodReference());
   }
 
-  default RetraceFrameResult retraceLinePosition(RetraceApi retracer) {
+  default RetraceFrameResult retraceLinePosition(Retracer retracer) {
     return retrace(retracer).narrowByPosition(getLineNumber());
   }
 
-  default RetraceFrameResult retracePcPosition(RetraceApi retracer, MethodSubject methodSubject) {
+  default RetraceFrameResult retracePcPosition(Retracer retracer, MethodSubject methodSubject) {
     return retrace(retracer).narrowByPosition(getOffset(methodSubject).offset);
   }
 }